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  }