github.com/andy2046/gopie@v0.7.0/pkg/ringhash/ringhash.go (about) 1 // Package ringhash provides a ring hash implementation. 2 package ringhash 3 4 import ( 5 "errors" 6 "hash/crc64" 7 "io" 8 "math" 9 "sort" 10 "strconv" 11 "sync" 12 ) 13 14 type ( 15 // Hash is the hash function. 16 Hash func(key string) uint64 17 18 // Node is the node in the ring. 19 Node struct { 20 Name string 21 Load int64 22 } 23 24 // Ring is the data store for keys hash map. 25 Ring struct { 26 hashFn Hash 27 replicas int 28 balancingFactor float64 29 hashKeyMap map[uint64]string 30 hashes []uint64 31 keyLoadMap map[string]*Node 32 totalLoad int64 33 34 mu sync.RWMutex 35 } 36 37 // Config is the config for hash ring. 38 Config struct { 39 HashFn Hash 40 Replicas int 41 BalancingFactor float64 42 } 43 44 // Option applies config to Config. 45 Option = func(*Config) error 46 ) 47 48 var ( 49 // hashCRC64 uses the 64-bit Cyclic Redundancy Check (CRC-64) with the ECMA polynomial. 50 hashCRC64 = crc64.New(crc64.MakeTable(crc64.ECMA)) 51 // ErrNoNode when there is no node added into the hash ring. 52 ErrNoNode = errors.New("no node added") 53 // ErrNodeNotFound when no node found in LoadMap. 54 ErrNodeNotFound = errors.New("node not found in LoadMap") 55 // DefaultConfig is the default config for hash ring. 56 DefaultConfig = Config{ 57 HashFn: hash, 58 Replicas: 10, 59 BalancingFactor: 1.25, 60 } 61 ) 62 63 func hash(key string) uint64 { 64 hashCRC64.Reset() 65 _, err := io.WriteString(hashCRC64, key) 66 if err != nil { 67 panic(err) 68 } 69 return hashCRC64.Sum64() 70 } 71 72 // New returns a new Ring. 73 func New(options ...Option) *Ring { 74 c := DefaultConfig 75 setOption(&c, options...) 76 r := &Ring{ 77 replicas: c.Replicas, 78 balancingFactor: c.BalancingFactor, 79 hashFn: c.HashFn, 80 hashKeyMap: make(map[uint64]string), 81 hashes: []uint64{}, 82 keyLoadMap: map[string]*Node{}, 83 } 84 return r 85 } 86 87 // IsEmpty returns true if there is no node in the ring. 88 func (r *Ring) IsEmpty() bool { 89 return len(r.hashKeyMap) == 0 90 } 91 92 // AddNode adds Node with key as name to the hash ring. 93 func (r *Ring) AddNode(keys ...string) { 94 r.mu.Lock() 95 defer r.mu.Unlock() 96 97 for _, key := range keys { 98 if _, ok := r.keyLoadMap[key]; ok { 99 continue 100 } 101 102 r.keyLoadMap[key] = &Node{Name: key, Load: 0} 103 104 for i := 0; i < r.replicas; i++ { 105 h := r.hashFn(key + strconv.Itoa(i)) 106 r.hashes = append(r.hashes, h) 107 r.hashKeyMap[h] = key 108 } 109 } 110 111 // sort hashes ascendingly 112 sort.Slice(r.hashes, func(i, j int) bool { 113 if r.hashes[i] < r.hashes[j] { 114 return true 115 } 116 return false 117 }) 118 } 119 120 // GetNode returns the closest node in the hash ring to the provided key. 121 func (r *Ring) GetNode(key string) (string, error) { 122 r.mu.RLock() 123 defer r.mu.RUnlock() 124 125 if r.IsEmpty() { 126 return "", ErrNoNode 127 } 128 129 h := r.hashFn(key) 130 idx := r.search(h) 131 return r.hashKeyMap[r.hashes[idx]], nil 132 } 133 134 // GetLeastNode uses consistent hashing with bounded loads to get the least loaded node. 135 func (r *Ring) GetLeastNode(key string) (string, error) { 136 r.mu.RLock() 137 defer r.mu.RUnlock() 138 139 if r.IsEmpty() { 140 return "", ErrNoNode 141 } 142 143 h := r.hashFn(key) 144 idx := r.search(h) 145 146 i := idx 147 var count int 148 for { 149 count++ 150 if count >= len(r.hashes) { 151 panic("not enough space to distribute load") 152 } 153 node := r.hashKeyMap[r.hashes[i]] 154 if r.loadOK(node) { 155 return node, nil 156 } 157 i++ 158 if i >= len(r.hashKeyMap) { 159 i = 0 160 } 161 } 162 } 163 164 // UpdateLoad sets load of the given node to the given load. 165 func (r *Ring) UpdateLoad(node string, load int64) { 166 r.mu.Lock() 167 defer r.mu.Unlock() 168 169 if _, ok := r.keyLoadMap[node]; !ok { 170 return 171 } 172 r.totalLoad -= r.keyLoadMap[node].Load 173 r.keyLoadMap[node].Load = load 174 r.totalLoad += load 175 } 176 177 // Add increases load of the given node by 1, 178 // should only be used with GetLeast. 179 func (r *Ring) Add(node string) bool { 180 r.mu.Lock() 181 defer r.mu.Unlock() 182 183 if _, ok := r.keyLoadMap[node]; !ok { 184 return false 185 } 186 r.keyLoadMap[node].Load++ 187 r.totalLoad++ 188 return true 189 } 190 191 // Done decreases load of the given node by 1, 192 // should only be used with GetLeast. 193 func (r *Ring) Done(node string) bool { 194 r.mu.Lock() 195 defer r.mu.Unlock() 196 197 if _, ok := r.keyLoadMap[node]; !ok { 198 return false 199 } 200 r.keyLoadMap[node].Load-- 201 r.totalLoad-- 202 return true 203 } 204 205 // RemoveNode deletes node from the hash ring. 206 func (r *Ring) RemoveNode(node string) bool { 207 r.mu.Lock() 208 defer r.mu.Unlock() 209 210 if _, ok := r.keyLoadMap[node]; !ok { 211 return false 212 } 213 214 for i := 0; i < r.replicas; i++ { 215 h := r.hashFn(node + strconv.Itoa(i)) 216 delete(r.hashKeyMap, h) 217 r.removeFromHashes(h) 218 } 219 delete(r.keyLoadMap, node) 220 return true 221 } 222 223 // Nodes returns the list of nodes in the hash ring. 224 func (r *Ring) Nodes() (nodes []string) { 225 r.mu.RLock() 226 defer r.mu.RUnlock() 227 228 for k := range r.keyLoadMap { 229 nodes = append(nodes, k) 230 } 231 return 232 } 233 234 // Loads returns the loads of all the nodes in the hash ring. 235 func (r *Ring) Loads() map[string]int64 { 236 r.mu.RLock() 237 defer r.mu.RUnlock() 238 239 loads := map[string]int64{} 240 for k, v := range r.keyLoadMap { 241 loads[k] = v.Load 242 } 243 return loads 244 } 245 246 // MaxLoad returns the maximum load for a single node in the hash ring, 247 // which is (totalLoad/numberOfNodes)*balancingFactor. 248 func (r *Ring) MaxLoad() int64 { 249 r.mu.RLock() 250 defer r.mu.RUnlock() 251 252 if r.totalLoad == 0 { 253 r.totalLoad = 1 254 } 255 var avgLoadPerNode float64 256 avgLoadPerNode = float64(r.totalLoad / int64(len(r.keyLoadMap))) 257 if avgLoadPerNode == 0 { 258 avgLoadPerNode = 1 259 } 260 avgLoadPerNode = math.Ceil(avgLoadPerNode * r.balancingFactor) 261 return int64(avgLoadPerNode) 262 } 263 264 func (r *Ring) removeFromHashes(h uint64) { 265 for i := 0; i < len(r.hashes); i++ { 266 if r.hashes[i] == h { 267 r.hashes = append(r.hashes[:i], r.hashes[i+1:]...) 268 } 269 } 270 } 271 272 func setOption(c *Config, options ...func(*Config) error) error { 273 for _, opt := range options { 274 if err := opt(c); err != nil { 275 return err 276 } 277 } 278 return nil 279 } 280 281 func (r *Ring) search(key uint64) int { 282 idx := sort.Search(len(r.hashes), func(i int) bool { 283 return r.hashes[i] >= key 284 }) 285 286 if idx >= len(r.hashes) { 287 idx = 0 288 } 289 return idx 290 } 291 292 func (r *Ring) loadOK(key string) bool { 293 if r.totalLoad < 0 { 294 r.totalLoad = 0 295 } 296 297 var avgLoadPerNode float64 298 avgLoadPerNode = float64((r.totalLoad + 1) / int64(len(r.keyLoadMap))) 299 if avgLoadPerNode == 0 { 300 avgLoadPerNode = 1 301 } 302 avgLoadPerNode = math.Ceil(avgLoadPerNode * r.balancingFactor) 303 304 node, ok := r.keyLoadMap[key] 305 if !ok { 306 panic(ErrNodeNotFound) 307 } 308 309 if float64(node.Load)+1 <= avgLoadPerNode { 310 return true 311 } 312 313 return false 314 }