github.com/lingyao2333/mo-zero@v1.4.1/core/hash/consistenthash.go (about)

     1  package hash
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strconv"
     7  	"sync"
     8  
     9  	"github.com/lingyao2333/mo-zero/core/lang"
    10  	"github.com/lingyao2333/mo-zero/core/mapping"
    11  )
    12  
    13  const (
    14  	// TopWeight is the top weight that one entry might set.
    15  	TopWeight = 100
    16  
    17  	minReplicas = 100
    18  	prime       = 16777619
    19  )
    20  
    21  type (
    22  	// Func defines the hash method.
    23  	Func func(data []byte) uint64
    24  
    25  	// A ConsistentHash is a ring hash implementation.
    26  	ConsistentHash struct {
    27  		hashFunc Func
    28  		replicas int
    29  		keys     []uint64
    30  		ring     map[uint64][]interface{}
    31  		nodes    map[string]lang.PlaceholderType
    32  		lock     sync.RWMutex
    33  	}
    34  )
    35  
    36  // NewConsistentHash returns a ConsistentHash.
    37  func NewConsistentHash() *ConsistentHash {
    38  	return NewCustomConsistentHash(minReplicas, Hash)
    39  }
    40  
    41  // NewCustomConsistentHash returns a ConsistentHash with given replicas and hash func.
    42  func NewCustomConsistentHash(replicas int, fn Func) *ConsistentHash {
    43  	if replicas < minReplicas {
    44  		replicas = minReplicas
    45  	}
    46  
    47  	if fn == nil {
    48  		fn = Hash
    49  	}
    50  
    51  	return &ConsistentHash{
    52  		hashFunc: fn,
    53  		replicas: replicas,
    54  		ring:     make(map[uint64][]interface{}),
    55  		nodes:    make(map[string]lang.PlaceholderType),
    56  	}
    57  }
    58  
    59  // Add adds the node with the number of h.replicas,
    60  // the later call will overwrite the replicas of the former calls.
    61  func (h *ConsistentHash) Add(node interface{}) {
    62  	h.AddWithReplicas(node, h.replicas)
    63  }
    64  
    65  // AddWithReplicas adds the node with the number of replicas,
    66  // replicas will be truncated to h.replicas if it's larger than h.replicas,
    67  // the later call will overwrite the replicas of the former calls.
    68  func (h *ConsistentHash) AddWithReplicas(node interface{}, replicas int) {
    69  	h.Remove(node)
    70  
    71  	if replicas > h.replicas {
    72  		replicas = h.replicas
    73  	}
    74  
    75  	nodeRepr := repr(node)
    76  	h.lock.Lock()
    77  	defer h.lock.Unlock()
    78  	h.addNode(nodeRepr)
    79  
    80  	for i := 0; i < replicas; i++ {
    81  		hash := h.hashFunc([]byte(nodeRepr + strconv.Itoa(i)))
    82  		h.keys = append(h.keys, hash)
    83  		h.ring[hash] = append(h.ring[hash], node)
    84  	}
    85  
    86  	sort.Slice(h.keys, func(i, j int) bool {
    87  		return h.keys[i] < h.keys[j]
    88  	})
    89  }
    90  
    91  // AddWithWeight adds the node with weight, the weight can be 1 to 100, indicates the percent,
    92  // the later call will overwrite the replicas of the former calls.
    93  func (h *ConsistentHash) AddWithWeight(node interface{}, weight int) {
    94  	// don't need to make sure weight not larger than TopWeight,
    95  	// because AddWithReplicas makes sure replicas cannot be larger than h.replicas
    96  	replicas := h.replicas * weight / TopWeight
    97  	h.AddWithReplicas(node, replicas)
    98  }
    99  
   100  // Get returns the corresponding node from h base on the given v.
   101  func (h *ConsistentHash) Get(v interface{}) (interface{}, bool) {
   102  	h.lock.RLock()
   103  	defer h.lock.RUnlock()
   104  
   105  	if len(h.ring) == 0 {
   106  		return nil, false
   107  	}
   108  
   109  	hash := h.hashFunc([]byte(repr(v)))
   110  	index := sort.Search(len(h.keys), func(i int) bool {
   111  		return h.keys[i] >= hash
   112  	}) % len(h.keys)
   113  
   114  	nodes := h.ring[h.keys[index]]
   115  	switch len(nodes) {
   116  	case 0:
   117  		return nil, false
   118  	case 1:
   119  		return nodes[0], true
   120  	default:
   121  		innerIndex := h.hashFunc([]byte(innerRepr(v)))
   122  		pos := int(innerIndex % uint64(len(nodes)))
   123  		return nodes[pos], true
   124  	}
   125  }
   126  
   127  // Remove removes the given node from h.
   128  func (h *ConsistentHash) Remove(node interface{}) {
   129  	nodeRepr := repr(node)
   130  
   131  	h.lock.Lock()
   132  	defer h.lock.Unlock()
   133  
   134  	if !h.containsNode(nodeRepr) {
   135  		return
   136  	}
   137  
   138  	for i := 0; i < h.replicas; i++ {
   139  		hash := h.hashFunc([]byte(nodeRepr + strconv.Itoa(i)))
   140  		index := sort.Search(len(h.keys), func(i int) bool {
   141  			return h.keys[i] >= hash
   142  		})
   143  		if index < len(h.keys) && h.keys[index] == hash {
   144  			h.keys = append(h.keys[:index], h.keys[index+1:]...)
   145  		}
   146  		h.removeRingNode(hash, nodeRepr)
   147  	}
   148  
   149  	h.removeNode(nodeRepr)
   150  }
   151  
   152  func (h *ConsistentHash) removeRingNode(hash uint64, nodeRepr string) {
   153  	if nodes, ok := h.ring[hash]; ok {
   154  		newNodes := nodes[:0]
   155  		for _, x := range nodes {
   156  			if repr(x) != nodeRepr {
   157  				newNodes = append(newNodes, x)
   158  			}
   159  		}
   160  		if len(newNodes) > 0 {
   161  			h.ring[hash] = newNodes
   162  		} else {
   163  			delete(h.ring, hash)
   164  		}
   165  	}
   166  }
   167  
   168  func (h *ConsistentHash) addNode(nodeRepr string) {
   169  	h.nodes[nodeRepr] = lang.Placeholder
   170  }
   171  
   172  func (h *ConsistentHash) containsNode(nodeRepr string) bool {
   173  	_, ok := h.nodes[nodeRepr]
   174  	return ok
   175  }
   176  
   177  func (h *ConsistentHash) removeNode(nodeRepr string) {
   178  	delete(h.nodes, nodeRepr)
   179  }
   180  
   181  func innerRepr(node interface{}) string {
   182  	return fmt.Sprintf("%d:%v", prime, node)
   183  }
   184  
   185  func repr(node interface{}) string {
   186  	return mapping.Repr(node)
   187  }