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  }