github.com/searKing/golang/go@v1.2.117/container/hashring/hashring.go (about) 1 // Copyright 2020 The searKing Author. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package hashring provides a consistent hashing function. 6 // 7 // NodeLocator hashing is often used to distribute requests to a changing set of servers. For example, 8 // say you have some cache servers cacheA, cacheB, and cacheC. You want to decide which cache server 9 // to use to look up information on a user. 10 // 11 // You could use a typical hash table and hash the user id 12 // to one of cacheA, cacheB, or cacheC. But with a typical hash table, if you add or remove a server, 13 // almost all keys will get remapped to different results, which basically could bring your service 14 // to a grinding halt while the caches get rebuilt. 15 // 16 // With a consistent hash, adding or removing a server drastically reduces the number of keys that 17 // get remapped. 18 // 19 // Read more about consistent hashing on wikipedia: http://en.wikipedia.org/wiki/Consistent_hashing 20 package hashring 21 22 import ( 23 "fmt" 24 "math" 25 "sort" 26 ) 27 28 // {} -> 127.0.0.1:11311 -> 127.0.0.1:11311-0 -> 1234 29 // Node -> Key -> IterateKey -> HashKey 30 // 31 // -> IterateKey -> HashKey 32 // -> IterateKey -> HashKey 33 type Node interface { 34 // Get the SocketAddress of the server to which this node is connected. 35 fmt.Stringer 36 } 37 38 const defaultNumReps = 160 39 40 type StringNode string 41 42 func (n StringNode) String() string { 43 return string(n) 44 } 45 46 // NodeLocator holds the information about the allNodes of the consistent hash nodes. 47 // 48 //go:generate go-option -type "NodeLocator" 49 type NodeLocator struct { 50 // The List of nodes to use in the Ketama consistent hash continuum 51 // 模拟一致性哈希环的环状结构,存放的都是可用节点 52 // 一致性Hash环 53 sortedKeys uint32Slice // []HashKey, Index for nodes binary search 54 nodeByKey map[uint32]Node // <HashKey,Node> 55 allNodes map[Node]struct{} // <Node> 56 57 // The hash algorithm to use when choosing a node in the Ketama consistent hash continuum 58 hashAlg HashAlgorithm 59 60 // node weights for ketama, a map from InetSocketAddress to weight as Integer 61 weightByNode map[Node]int 62 isWeighted bool 63 64 // the number of discrete hashes that should be defined for each node in the continuum. 65 numReps int 66 // the format used to name the nodes in Ketama, either SpyMemcached or LibMemcached 67 nodeKeyFormatter *KetamaNodeKeyFormatter 68 } 69 70 // New creates a hash ring of n replicas for each entry. 71 func New(opts ...NodeLocatorOption) *NodeLocator { 72 r := &NodeLocator{ 73 nodeByKey: make(map[uint32]Node), 74 allNodes: make(map[Node]struct{}), 75 hashAlg: KetamaHash, 76 weightByNode: make(map[Node]int), 77 numReps: defaultNumReps, 78 nodeKeyFormatter: NewKetamaNodeKeyFormatter(SpyMemcached), 79 } 80 r.ApplyOptions(opts...) 81 return r 82 } 83 84 // GetAllNodes returns all available nodes 85 func (c *NodeLocator) GetAllNodes() []Node { 86 var nodes []Node 87 for node := range c.allNodes { 88 nodes = append(nodes, node) 89 } 90 return nodes 91 } 92 93 // GetPrimaryNode returns the first available node for a name, such as “127.0.0.1:11311-0” for "Alice" 94 func (c *NodeLocator) GetPrimaryNode(name string) (Node, bool) { 95 return c.getNodeForHashKey(c.getHashKey(name)) 96 } 97 98 // GetMaxHashKey returns the last available node's HashKey 99 // that is, Maximum HashKey in the Hash Cycle 100 func (c *NodeLocator) GetMaxHashKey() (uint32, error) { 101 if len(c.sortedKeys) == 0 { 102 return 0, fmt.Errorf("NoSuchElementException") 103 } 104 return c.sortedKeys[len(c.sortedKeys)-1], nil 105 } 106 107 // getNodeForHashKey returns the first available node since iterateHashKey, such as HASH(“127.0.0.1:11311-0”) 108 func (c *NodeLocator) getNodeForHashKey(hash uint32) (Node, bool) { 109 if len(c.sortedKeys) == 0 { 110 return nil, false 111 } 112 113 rv, has := c.getNodeByKey()[hash] 114 if has { 115 return rv, true 116 } 117 firstKey, found := c.tailSearch(hash) 118 if !found { 119 firstKey = 0 120 } 121 122 hash = c.sortedKeys[firstKey] 123 rv, has = c.getNodeByKey()[hash] 124 return rv, has 125 } 126 127 // 根据输入物理节点列表,重新构造Hash环,即虚拟节点环 128 // updateLocator reconstructs the hash ring with the input nodes 129 func (c *NodeLocator) updateLocator(nodes ...Node) { 130 c.SetNodes(nodes...) 131 } 132 133 // GetNodeRepetitions returns the number of discrete hashes that should be defined for each node 134 // in the continuum. 135 func (c *NodeLocator) getNodeRepetitions() int { 136 return c.numReps 137 } 138 139 // getNodeByKey returns the nodes 140 func (c *NodeLocator) getNodeByKey() map[uint32]Node { 141 return c.nodeByKey 142 } 143 144 // SetNodes setups the NodeLocator with the list of nodes it should use. 145 // If there are existing nodes not present in nodes, they will be removed. 146 // @param nodes a List of Nodes for this NodeLocator to use in 147 // its continuum 148 func (c *NodeLocator) SetNodes(nodes ...Node) { 149 if c.isWeighted { 150 c.setWeightNodes(nodes...) 151 return 152 } 153 c.setNoWeightNodes(nodes...) 154 } 155 156 func (c *NodeLocator) setNoWeightNodes(nodes ...Node) { 157 // Set sets all the elements in the hash. 158 // If there are existing elements not present in nodes, they will be removed. 159 var nodesToBeRemoved []Node 160 // remove missing Nodes 161 for k := range c.allNodes { 162 var found bool 163 for _, v := range nodes { 164 if k.String() == v.String() { 165 // found 166 found = true 167 break 168 } 169 } 170 if !found { 171 nodesToBeRemoved = append(nodesToBeRemoved, k) 172 } 173 } 174 if len(nodesToBeRemoved) == len(nodes) { 175 c.RemoveAllNodes() 176 } else { 177 c.removeNoWeightNodes(nodesToBeRemoved...) 178 } 179 // add all missing elements present in nodes. 180 var nodesToBeAdded []Node 181 for _, k := range nodes { 182 var found bool 183 for v := range c.allNodes { 184 if k.String() == v.String() { 185 found = true 186 break 187 } 188 } 189 if !found { 190 nodesToBeAdded = append(nodesToBeAdded, k) 191 } 192 } 193 c.addNoWeightNodes(nodesToBeAdded...) 194 } 195 196 func (c *NodeLocator) setWeightNodes(nodes ...Node) { 197 c.RemoveAllNodes() 198 numReps := c.getNodeRepetitions() 199 nodeCount := len(nodes) 200 totalWeight := 0 201 202 for _, node := range nodes { 203 totalWeight += c.weightByNode[node] 204 } 205 206 // add all elements present in nodes. 207 for _, node := range nodes { 208 thisWeight := c.weightByNode[node] 209 percent := float64(thisWeight) / float64(totalWeight) 210 // floor(percent * numReps * nodeCount + 1e10) 211 pointerPerServer := (int)(math.Floor(percent*(float64(numReps))*float64(nodeCount) + 0.0000000001)) 212 c.addNodeWithoutSort(node, pointerPerServer) 213 } 214 215 // sort keys 216 c.updateSortedNodes() 217 } 218 219 // RemoveAllNodes removes all nodes in the continuum.... 220 func (c *NodeLocator) RemoveAllNodes() { 221 c.sortedKeys = nil 222 c.nodeByKey = make(map[uint32]Node) 223 c.allNodes = make(map[Node]struct{}) 224 } 225 226 // AddNodes inserts nodes into the consistent hash cycle. 227 func (c *NodeLocator) AddNodes(nodes ...Node) { 228 if c.isWeighted { 229 c.addWeightNodes(nodes...) 230 return 231 } 232 c.addNoWeightNodes(nodes...) 233 } 234 235 func (c *NodeLocator) addWeightNodes(nodes ...Node) { 236 c.setWeightNodes(append(c.GetAllNodes(), nodes...)...) 237 } 238 239 func (c *NodeLocator) addNoWeightNodes(nodes ...Node) { 240 numReps := c.getNodeRepetitions() 241 242 for _, node := range nodes { 243 c.addNodeWithoutSort(node, numReps) 244 } 245 246 c.updateSortedNodes() 247 } 248 249 func (c *NodeLocator) addNodeWithoutSort(node Node, numReps int) { 250 // Ketama does some special work with md5 where it reuses chunks. 251 // Check to be backwards compatible, the hash algorithm does not 252 // matter for Ketama, just the placement should always be done using 253 // MD5 254 255 // KETAMA_HASH, Special Case, batch mode to speedup 256 257 for i := 0; i < numReps; { 258 positions := c.getIterateHashKeyForNode(node, i) 259 if len(positions) == 0 { 260 numReps++ 261 i++ // ignore no hash node 262 break 263 } 264 265 for j, pos := range positions { 266 if i+j > numReps { // out of bound 267 break 268 } 269 if _, has := c.getNodeByKey()[pos]; has { 270 // skip this node, duplicated 271 numReps++ 272 continue 273 } 274 c.getNodeByKey()[pos] = node 275 } 276 i += len(positions) 277 } 278 279 c.allNodes[node] = struct{}{} 280 } 281 282 // Remove removes nodes from the consistent hash cycle... 283 func (c *NodeLocator) RemoveNodes(nodes ...Node) { 284 if c.isWeighted { 285 c.removeWeightNodes(nodes...) 286 return 287 } 288 c.removeNoWeightNodes(nodes...) 289 } 290 291 func (c *NodeLocator) removeWeightNodes(nodes ...Node) { 292 for _, node := range nodes { 293 delete(c.allNodes, node) 294 } 295 c.setWeightNodes(c.GetAllNodes()...) 296 } 297 298 func (c *NodeLocator) removeNoWeightNodes(nodes ...Node) { 299 numReps := c.getNodeRepetitions() 300 301 for _, node := range nodes { 302 for i := 0; i < numReps; { 303 positions := c.getIterateHashKeyForNode(node, i) 304 if len(positions) == 0 { 305 // ignore no hash node 306 numReps++ 307 i++ 308 continue 309 } 310 311 for j, pos := range positions { 312 if i+j > numReps { // out of bound 313 break 314 } 315 if n, has := c.nodeByKey[pos]; has { 316 if n.String() != node.String() { 317 numReps++ // ignore no hash node 318 continue 319 } 320 delete(c.nodeByKey, pos) 321 } 322 } 323 i += len(positions) 324 } 325 delete(c.allNodes, node) 326 } 327 c.updateSortedNodes() 328 } 329 330 // tailSearch returns the first available node since iterateHashKey's Index, such as Index(HASH(“127.0.0.1:11311-0”)) 331 func (c *NodeLocator) tailSearch(key uint32) (i int, found bool) { 332 found = true 333 f := func(x int) bool { 334 return c.sortedKeys[x] >= key 335 } 336 // Search uses binary search to find and return the smallest index since iterateHashKey's Index 337 i = sort.Search(len(c.sortedKeys), f) 338 if i >= len(c.sortedKeys) { 339 found = false 340 } 341 return 342 } 343 344 // Get returns an element close to where name hashes to in the nodes. 345 func (c *NodeLocator) Get(name string) (Node, bool) { 346 if len(c.nodeByKey) == 0 { 347 return nil, false 348 } 349 return c.GetPrimaryNode(name) 350 } 351 352 // GetTwo returns the two closest distinct elements to the name input in the nodes. 353 func (c *NodeLocator) GetTwo(name string) (Node, Node, bool) { 354 if len(c.getNodeByKey()) == 0 { 355 return nil, nil, false 356 } 357 key := c.getHashKey(name) 358 firstKey, found := c.tailSearch(key) 359 if !found { 360 firstKey = 0 361 } 362 firstNode, has := c.getNodeByKey()[c.sortedKeys[firstKey]] 363 364 if len(c.allNodes) == 1 { 365 return firstNode, nil, has 366 } 367 368 start := firstKey 369 var secondNode Node 370 for i := start + 1; i != start; i++ { 371 if i >= len(c.sortedKeys) { 372 i = 0 373 } 374 secondNode = c.getNodeByKey()[c.sortedKeys[i]] 375 if secondNode.String() != firstNode.String() { 376 break 377 } 378 } 379 return firstNode, secondNode, true 380 } 381 382 // GetN returns the N closest distinct elements to the name input in the nodes. 383 func (c *NodeLocator) GetN(name string, n int) ([]Node, bool) { 384 if len(c.getNodeByKey()) == 0 { 385 return nil, false 386 } 387 388 if len(c.getNodeByKey()) < n { 389 n = len(c.getNodeByKey()) 390 } 391 392 key := c.getHashKey(name) 393 firstKey, found := c.tailSearch(key) 394 if !found { 395 firstKey = 0 396 } 397 firstNode, has := c.getNodeByKey()[c.sortedKeys[firstKey]] 398 399 nodes := make([]Node, 0, n) 400 nodes = append(nodes, firstNode) 401 402 if len(nodes) == n { 403 return nodes, has 404 } 405 406 start := firstKey 407 var secondNode Node 408 for i := start + 1; i != start; i++ { 409 if i >= len(c.sortedKeys) { 410 i = 0 411 // take care of i++ after this loop of for 412 i-- 413 continue 414 } 415 secondNode = c.getNodeByKey()[c.sortedKeys[i]] 416 if !sliceContainsMember(nodes, secondNode) { 417 nodes = append(nodes, secondNode) 418 } 419 if len(nodes) == n { 420 break 421 } 422 } 423 424 return nodes, true 425 } 426 427 func (c *NodeLocator) updateSortedNodes() { 428 hashes := c.sortedKeys[:0] 429 // reallocate if we're holding on to too much (1/4th) 430 // len(nodes) * replicas < cap / 4 431 // len(c.nodeByKey) ≈ len(c.allNodes)*c.numReps 432 if cap(c.sortedKeys)/4 > len(c.nodeByKey) { 433 hashes = nil 434 } 435 for k := range c.nodeByKey { 436 hashes = append(hashes, k) 437 } 438 sort.Sort(hashes) 439 c.sortedKeys = hashes 440 } 441 442 func sliceContainsMember(set []Node, member Node) bool { 443 for _, m := range set { 444 if m.String() == member.String() { 445 return true 446 } 447 } 448 return false 449 }