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 }