github.phpd.cn/thought-machine/please@v12.2.0+incompatible/tools/cache/cluster/cluster.go (about)

     1  // Package cluster contains functions for dealing with a cluster of plz cache nodes.
     2  //
     3  // Clustering the cache provides redundancy and increased performance
     4  // for large caches. Right now the functionality is a little limited,
     5  // there's no online rehashing so the size must be declared and fixed
     6  // up front, and the replication factor is fixed at 2. There's an
     7  // assumption that while nodes might restart, they return with the same
     8  // name which we use to re-identify them.
     9  //
    10  // The general approach here errs heavily on the side of simplicity and
    11  // less on zero-downtime reliability since, at the end of the day, this
    12  // is only a cache server.
    13  package cluster
    14  
    15  import (
    16  	"bytes"
    17  	"context"
    18  	"fmt"
    19  	stdlog "log"
    20  	"net"
    21  	"os"
    22  	"strconv"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/grpc-ecosystem/go-grpc-middleware/retry"
    28  	"github.com/hashicorp/memberlist"
    29  	"google.golang.org/grpc"
    30  	"gopkg.in/op/go-logging.v1"
    31  
    32  	pb "cache/proto/rpc_cache"
    33  	"cache/tools"
    34  )
    35  
    36  var log = logging.MustGetLogger("cluster")
    37  
    38  // A Cluster handles communication between a set of clustered cache servers.
    39  type Cluster struct {
    40  	list *memberlist.Memberlist
    41  	// nodes is a list of nodes that is initialised by the original seed
    42  	// and replicated between any other nodes that join after.
    43  	nodes []*pb.Node
    44  	// nodeMutex protects access to nodes
    45  	nodeMutex sync.RWMutex
    46  
    47  	// clients is a pool of gRPC clients to the other cluster nodes.
    48  	clients map[string]pb.RpcServerClient
    49  	// clientMutex protects concurrent access to clients.
    50  	clientMutex sync.RWMutex
    51  
    52  	// size is the expected number of nodes in the cluster.
    53  	size int
    54  
    55  	// node is the node corresponding to this instance.
    56  	node *pb.Node
    57  
    58  	// hostname is our hostname.
    59  	hostname string
    60  	// name is the name of this cluster node.
    61  	name string
    62  }
    63  
    64  // NewCluster creates a new Cluster object and starts listening on the given port.
    65  func NewCluster(port, rpcPort int, name, advertiseAddr string) *Cluster {
    66  	c := memberlist.DefaultLANConfig()
    67  	c.BindPort = port
    68  	c.AdvertisePort = port
    69  	c.Delegate = &delegate{name: name, port: rpcPort}
    70  	c.Logger = stdlog.New(&logWriter{}, "", 0)
    71  	c.AdvertiseAddr = advertiseAddr
    72  	if name != "" {
    73  		c.Name = name
    74  	}
    75  	list, err := memberlist.Create(c)
    76  	if err != nil {
    77  		log.Fatalf("Failed to create new memberlist: %s", err)
    78  	}
    79  	clu := &Cluster{
    80  		clients: map[string]pb.RpcServerClient{},
    81  		name:    name,
    82  		list:    list,
    83  	}
    84  	if hostname, err := os.Hostname(); err == nil {
    85  		clu.hostname = hostname
    86  	}
    87  	n := list.LocalNode()
    88  	log.Notice("Memberlist initialised, this node is %s / %s:%d", n.Name, n.Addr, port)
    89  	return clu
    90  }
    91  
    92  // Join joins an existing plz cache cluster.
    93  func (cluster *Cluster) Join(members []string) {
    94  	// Talk to the other nodes to request to join.
    95  	if _, err := cluster.list.Join(members); err != nil {
    96  		log.Fatalf("Failed to join cluster: %s", err)
    97  	}
    98  	for _, node := range cluster.list.Members() {
    99  		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   100  		defer cancel()
   101  		name, port := cluster.metadata(node)
   102  		if name == cluster.name {
   103  			continue // Don't attempt to join ourselves, we're in the memberlist but can't welcome a new member.
   104  		}
   105  		log.Notice("Attempting to join with %s: %s / %s", name, node.Addr, port)
   106  		if client, err := cluster.getRPCClient(name, node.Addr.String()+port); err != nil {
   107  			log.Error("Error getting RPC client for %s: %s", node.Addr, err)
   108  		} else if resp, err := client.Join(ctx, &pb.JoinRequest{
   109  			Name:    cluster.list.LocalNode().Name,
   110  			Address: cluster.list.LocalNode().Addr.String(),
   111  		}); err != nil {
   112  			log.Error("Error communicating with %s: %s", node.Addr, err)
   113  		} else if !resp.Success {
   114  			log.Fatalf("We have not been allowed to join the cluster :(")
   115  		} else {
   116  			cluster.nodes = resp.Nodes
   117  			cluster.node = resp.Node
   118  			cluster.size = int(resp.Size)
   119  			return
   120  		}
   121  	}
   122  	log.Fatalf("Unable to contact any other cluster members")
   123  }
   124  
   125  // metadata breaks metadata from a node into its name and port (with a leading colon).
   126  func (cluster *Cluster) metadata(node *memberlist.Node) (string, string) {
   127  	meta := string(node.Meta)
   128  	idx := strings.IndexRune(meta, ':')
   129  	if idx == -1 {
   130  		return "", ""
   131  	}
   132  	return meta[:idx], meta[idx:]
   133  }
   134  
   135  // Init seeds a new plz cache cluster.
   136  func (cluster *Cluster) Init(size int) {
   137  	cluster.size = size
   138  	// We're node 0. The following will add us to the node list.
   139  	cluster.node = cluster.newNode(cluster.list.LocalNode())
   140  	// And there aren't any others yet, so we're done.
   141  }
   142  
   143  // GetMembers returns the set of currently known cache members.
   144  func (cluster *Cluster) GetMembers() []*pb.Node {
   145  	// TODO(pebers): this is quadratic so would be bad on large clusters.
   146  	// We might also want to refresh the members in the background if this proves slow?
   147  	for _, m := range cluster.list.Members() {
   148  		cluster.newNode(m)
   149  	}
   150  	return cluster.nodes[:]
   151  }
   152  
   153  // newNode constructs one of our canonical nodes from a memberlist.Node.
   154  // This includes allocating it hash space.
   155  func (cluster *Cluster) newNode(node *memberlist.Node) *pb.Node {
   156  	newNode := func(i int) *pb.Node {
   157  		_, port := cluster.metadata(node)
   158  		return &pb.Node{
   159  			Name:      node.Name,
   160  			Address:   node.Addr.String() + port,
   161  			HashBegin: tools.HashPoint(i, cluster.size),
   162  			HashEnd:   tools.HashPoint(i+1, cluster.size),
   163  		}
   164  	}
   165  	cluster.nodeMutex.Lock()
   166  	defer cluster.nodeMutex.Unlock()
   167  	for i, n := range cluster.nodes {
   168  		if n.Name == "" || n.Name == node.Name {
   169  			// Available slot. Or, if they identified as an existing node, they can take that space over.
   170  			if n.Name == node.Name {
   171  				log.Notice("Node %s / %s matched to slot %d", node.Name, node.Addr, i)
   172  			} else {
   173  				log.Notice("Populating node %d: %s / %s", i, node.Name, node.Addr)
   174  			}
   175  			cluster.nodes[i] = newNode(i)
   176  			// Remove any client that might exist for this node so we force a reconnection.
   177  			cluster.clientMutex.Lock()
   178  			defer cluster.clientMutex.Unlock()
   179  			delete(cluster.clients, n.Name)
   180  			return cluster.nodes[i]
   181  		}
   182  	}
   183  	if len(cluster.nodes) < cluster.size {
   184  		node := newNode(len(cluster.nodes))
   185  		cluster.nodes = append(cluster.nodes, node)
   186  		return node
   187  	}
   188  	log.Warning("Node %s / %s attempted to join, but there is no space available [%d / %d].", node.Name, node.Addr, len(cluster.nodes), cluster.size)
   189  	return nil
   190  }
   191  
   192  // getRPCClient returns an RPC client for the given server.
   193  func (cluster *Cluster) getRPCClient(name, address string) (pb.RpcServerClient, error) {
   194  	cluster.clientMutex.RLock()
   195  	client, present := cluster.clients[name]
   196  	cluster.clientMutex.RUnlock()
   197  	if present {
   198  		return client, nil
   199  	}
   200  	// TODO(pebers): add credentials.
   201  	connection, err := grpc.Dial(address, grpc.WithTimeout(5*time.Second), grpc.WithInsecure(),
   202  		grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor(grpc_retry.WithMax(3))))
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  	client = pb.NewRpcServerClient(connection)
   207  	if name != "" {
   208  		cluster.clientMutex.Lock()
   209  		cluster.clients[name] = client
   210  		cluster.clientMutex.Unlock()
   211  	}
   212  	return client, nil
   213  }
   214  
   215  // getAlternateNode returns the replica node for the given hash (i.e. whichever one is not us,
   216  // we don't really know for sure when calling this if we are the primary or not).
   217  func (cluster *Cluster) getAlternateNode(hash []byte) (string, string) {
   218  	point := tools.Hash(hash)
   219  	if point >= cluster.node.HashBegin && point < cluster.node.HashEnd {
   220  		// We've got this point, use the alternate.
   221  		point = tools.AlternateHash(hash)
   222  	}
   223  	cluster.nodeMutex.RLock()
   224  	defer cluster.nodeMutex.RUnlock()
   225  	for _, n := range cluster.nodes {
   226  		if point >= n.HashBegin && point < n.HashEnd {
   227  			return n.Name, n.Address
   228  		}
   229  	}
   230  	log.Warning("No cluster node found for hash point %d", point)
   231  	return "", ""
   232  }
   233  
   234  // ReplicateArtifacts replicates artifacts from this node to another.
   235  func (cluster *Cluster) ReplicateArtifacts(req *pb.StoreRequest) {
   236  	name, address := cluster.getAlternateNode(req.Hash)
   237  	if address == "" {
   238  		log.Warning("Couldn't get alternate address, will not replicate artifact")
   239  		return
   240  	}
   241  	log.Info("Replicating artifact to node %s", address)
   242  	cluster.replicate(name, address, req.Os, req.Arch, req.Hash, false, req.Artifacts, req.Hostname)
   243  }
   244  
   245  // DeleteArtifacts deletes artifacts from all other nodes.
   246  func (cluster *Cluster) DeleteArtifacts(req *pb.DeleteRequest) {
   247  	for _, node := range cluster.GetMembers() {
   248  		// Don't forward request to ourselves...
   249  		if cluster.node.Name != node.Name {
   250  			log.Info("Forwarding delete request to node %s", node.Address)
   251  			cluster.replicate(node.Name, node.Address, req.Os, req.Arch, nil, true, req.Artifacts, "")
   252  		}
   253  	}
   254  }
   255  
   256  func (cluster *Cluster) replicate(name, address, os, arch string, hash []byte, delete bool, artifacts []*pb.Artifact, hostname string) {
   257  	client, err := cluster.getRPCClient(name, address)
   258  	if err != nil {
   259  		log.Error("Failed to get RPC client for %s %s: %s", name, address, err)
   260  		return
   261  	}
   262  	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
   263  	defer cancel()
   264  	if resp, err := client.Replicate(ctx, &pb.ReplicateRequest{
   265  		Artifacts: artifacts,
   266  		Os:        os,
   267  		Arch:      arch,
   268  		Hash:      hash,
   269  		Delete:    delete,
   270  		Hostname:  hostname,
   271  		Peer:      cluster.hostname,
   272  	}); err != nil {
   273  		log.Error("Error replicating artifact: %s", err)
   274  	} else if !resp.Success {
   275  		log.Error("Failed to replicate artifact to %s", address)
   276  	}
   277  }
   278  
   279  // AddNode adds a new node that's applying to join the cluster.
   280  func (cluster *Cluster) AddNode(req *pb.JoinRequest) *pb.JoinResponse {
   281  	node := cluster.newNode(&memberlist.Node{
   282  		Name: req.Name,
   283  		Addr: net.ParseIP(req.Address),
   284  	})
   285  	if node == nil {
   286  		log.Warning("Rejected join request from %s", req.Address)
   287  		return &pb.JoinResponse{Success: false}
   288  	}
   289  	return &pb.JoinResponse{
   290  		Success: true,
   291  		Nodes:   cluster.GetMembers(),
   292  		Node:    node,
   293  		Size:    int32(cluster.size),
   294  	}
   295  }
   296  
   297  // A delegate is our implementation of memberlist's Delegate interface.
   298  // Somewhat awkwardly we have to implement the whole thing to provide metadata for our node,
   299  // which we only really need to do to communicate our name and RPC port.
   300  type delegate struct {
   301  	name string
   302  	port int
   303  }
   304  
   305  func (d *delegate) NodeMeta(limit int) []byte {
   306  	return []byte(d.name + ":" + strconv.Itoa(d.port))
   307  }
   308  
   309  func (d *delegate) NotifyMsg([]byte)                           {}
   310  func (d *delegate) GetBroadcasts(overhead, limit int) [][]byte { return nil }
   311  func (d *delegate) LocalState(join bool) []byte                { return nil }
   312  func (d *delegate) MergeRemoteState(buf []byte, join bool)     {}
   313  
   314  // A logWriter is a wrapper around our logger to decode memberlist's prefixes into our logging levels.
   315  type logWriter struct{}
   316  
   317  // logLevels maps memberlist's prefixes to our logging levels.
   318  var logLevels = map[string]func(format string, args ...interface{}){
   319  	"[ERR]":   log.Errorf,
   320  	"[ERROR]": log.Errorf,
   321  	"[WARN]":  log.Warning,
   322  	"[INFO]":  log.Info,
   323  	"[DEBUG]": log.Debug,
   324  }
   325  
   326  // Write implements the io.Writer interface
   327  func (w *logWriter) Write(b []byte) (int, error) {
   328  	for prefix, f := range logLevels {
   329  		if bytes.HasPrefix(b, []byte(prefix)) {
   330  			f(string(bytes.TrimSpace(bytes.TrimPrefix(b, []byte(prefix)))))
   331  			return len(b), nil
   332  		}
   333  	}
   334  	return 0, fmt.Errorf("Couldn't decide how to log %s", string(b))
   335  }