-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconsistent_hash.py
71 lines (51 loc) · 1.75 KB
/
consistent_hash.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import sys
import hashlib
import struct
import bisect
VALUE_IDX = 2
HASH_IDX = 3
LAST = -1
FIRST = 0
class ConsistentHash:
def __init__(self, kvlist, replica, hash_func=None):
self.hash_func = hash_func
if not self.hash_func:
self.hash_func = self.ketama_hash
self.kvlist = kvlist
self.replica = replica
self.continuum = self.rebuild(kvlist)
def ketama_hash(self, key: str):
key = key.encode('utf-8')
return struct.unpack('<I', hashlib.md5(key).digest()[0:4])[0]
def rebuild(self, kvlist):
continuum = [
(k, i, v, self._hash("%s:%s" % (nick, i)), "%s:%s" % (nick, i))
for k, nick, v in kvlist for i in range(self.replica)
]
continuum.sort(key=lambda x: x[HASH_IDX])
return continuum
def _hash(self, key):
return self.hash_func(key)
def find_near_value(self, continuum, h):
hashes = [item[HASH_IDX] for item in continuum]
# use binary search to find the first item that is greater than h
idx = bisect.bisect_left(hashes, h)
if idx == len(hashes):
idx = 0
return idx, continuum[idx][VALUE_IDX]
def get(self, key):
h = self._hash(key)
if h < self.continuum[FIRST][HASH_IDX] or h > self.continuum[LAST][HASH_IDX]:
return 0, self.continuum[FIRST][VALUE_IDX]
return self.find_near_value(self.continuum, h)
if __name__ == "__main__":
replica = 2
kvlist = [
("host1", "cache1", "value1"),
("host2", "cache2", "value2"),
("host3", "cache3", "value3"),
("host4", "cache4", "value4")
]
ch = ConsistentHash(kvlist, replica)
v = ch.get(sys.argv[1])
print(v[0], ch.continuum[v[0]])