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 }