@@ -19,24 +19,81 @@ package consistenthash
1919
2020import (
2121 "hash/crc32"
22+ "math/bits"
2223 "sort"
2324 "strconv"
2425)
2526
2627type Hash func (data []byte ) uint32
2728
29+ const defaultHashExpansion = 6
30+
2831type Map struct {
29- hash Hash
30- replicas int
31- keys []int // Sorted
32- hashMap map [int ]string
32+ // Inputs
33+
34+ // hash is the hash function that will be applied to both added
35+ // keys and fetched keys
36+ hash Hash
37+
38+ // replicas is the number of virtual nodes that will be inserted
39+ // into the consistent hash ring for each key added
40+ replicas int
41+
42+ // prefixTableExpansion is the multiple of virtual nodes that
43+ // will be inserted into the internal hash table for O(1) lookups.
44+ prefixTableExpansion int
45+
46+
47+
48+ // Internal data
49+
50+ // keys is the hash of the virtual nodes, sorted by hash value
51+ keys []int // Sorted
52+
53+ // hashMap maps the hashed keys back to the input strings.
54+ // Note that all virtual nodes will map back to the same input
55+ // string
56+ hashMap map [int ]string
57+
58+ // prefixShift is the number of bits an input hash should
59+ // be right-shifted to act as a lookup in the prefixTable
60+ prefixShift uint32
61+
62+ // prefixTable is a map of the most significant bits of
63+ // a hash value to output all hashes with that prefix
64+ // map to. If the result is ambiguous (i.e. there is a
65+ // hash range split within this prefix) the value will
66+ // be blank and we should fall back to a binary search
67+ // through keys to find the exact output
68+ prefixTable []string
3369}
3470
71+ // New returns a blank consistent hash ring that will return
72+ // the key whos hash comes next after the hash of the input to
73+ // Get().
74+ // Increasing the number of replicas will improve the smoothness
75+ // of the hash ring and reduce the data moved when adding/removing
76+ // nodes, at the cost of more memory.
3577func New (replicas int , fn Hash ) * Map {
78+ return NewConsistentHash (replicas , defaultHashExpansion , fn )
79+ }
80+
81+
82+ // NewConsistentHash returns a blank consistent hash ring that will return
83+ // the key whos hash comes next after the hash of the input to
84+ // Get().
85+ // Increasing the number of replicas will improve the smoothness
86+ // of the hash ring and reduce the data moved when adding/removing
87+ // nodes.
88+ // Increasing the tableExpansion will allocate more entries in the
89+ // internal hash table, reducing the frequency of lg(n) binary
90+ // searches during Get() calls.
91+ func NewConsistentHash (replicas int , tableExpansion int , fn Hash ) * Map {
3692 m := & Map {
37- replicas : replicas ,
38- hash : fn ,
39- hashMap : make (map [int ]string ),
93+ replicas : replicas ,
94+ hash : fn ,
95+ hashMap : make (map [int ]string ),
96+ prefixTableExpansion : tableExpansion ,
4097 }
4198 if m .hash == nil {
4299 m .hash = crc32 .ChecksumIEEE
@@ -59,6 +116,37 @@ func (m *Map) Add(keys ...string) {
59116 }
60117 }
61118 sort .Ints (m .keys )
119+
120+ // Find minimum number of bits to hold |keys| * prefixTableExpansion
121+ prefixBits := uint32 (bits .Len32 (uint32 (len (m .keys ) * m .prefixTableExpansion )))
122+ m .prefixShift = 32 - prefixBits
123+
124+ prefixTableSize := 1 << prefixBits
125+ m .prefixTable = make ([]string , prefixTableSize )
126+
127+ previousKeyPrefix := - 1 // Effectively -Inf
128+ currentKeyIdx := 0
129+ currentKeyPrefix := m .keys [currentKeyIdx ] >> m .prefixShift
130+
131+ for i := range m .prefixTable {
132+ if previousKeyPrefix < i && currentKeyPrefix > i {
133+ // All keys with this prefix will map to a single value
134+ m .prefixTable [i ] = m.hashMap [m.keys [currentKeyIdx ]]
135+ } else {
136+ // Several keys might have the same prefix. Walk
137+ // over them until it changes
138+ previousKeyPrefix = currentKeyPrefix
139+ for currentKeyPrefix == previousKeyPrefix {
140+ currentKeyIdx ++
141+ if currentKeyIdx < len (m .keys ) {
142+ currentKeyPrefix = m .keys [currentKeyIdx ] >> m .prefixShift
143+ } else {
144+ currentKeyIdx = 0
145+ currentKeyPrefix = prefixTableSize + 1 // Effectively +Inf
146+ }
147+ }
148+ }
149+ }
62150}
63151
64152// Gets the closest item in the hash to the provided key.
@@ -69,6 +157,13 @@ func (m *Map) Get(key string) string {
69157
70158 hash := int (m .hash ([]byte (key )))
71159
160+ // Look for the hash prefix in the prefix table
161+ prefixSlot := hash >> m .prefixShift
162+ tableResult := m .prefixTable [prefixSlot ]
163+ if len (tableResult ) > 0 {
164+ return tableResult
165+ }
166+
72167 // Binary search for appropriate replica.
73168 idx := sort .Search (len (m .keys ), func (i int ) bool { return m .keys [i ] >= hash })
74169
0 commit comments