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