-
Notifications
You must be signed in to change notification settings - Fork 0
/
ringhash.go
132 lines (112 loc) · 2.86 KB
/
ringhash.go
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Package ringhash implementats a consistent ring hash:
// https://en.wikipedia.org/wiki/Consistent_hashing
package ringhash
import (
"encoding/ascii85"
"hash/fnv"
"log"
"sort"
"strconv"
)
// Hash is a signature of a hash function used by the package.
type Hash func(data []byte) uint32
type elem struct {
key string
hash uint32
}
type sortable []elem
func (k sortable) Len() int { return len(k) }
func (k sortable) Swap(i, j int) { k[i], k[j] = k[j], k[i] }
func (k sortable) Less(i, j int) bool {
// Weak hash function may cause collisions.
if k[i].hash < k[j].hash {
return true
}
if k[i].hash == k[j].hash {
return k[i].key < k[j].key
}
return false
}
// Ring is the definition of the ringhash.
type Ring struct {
keys []elem // Sorted list of keys.
signature string
replicas int
hashfunc Hash
}
// New initializes an empty ringhash with the given number of replicas and a hash function.
// If the hash function is nil, fnv.New32a() is used.
func New(replicas int, fn Hash) *Ring {
ring := &Ring{
replicas: replicas,
hashfunc: fn,
}
if ring.hashfunc == nil {
ring.hashfunc = func(data []byte) uint32 {
hash := fnv.New32a()
hash.Write(data)
return hash.Sum32()
}
}
return ring
}
// Len returns the number of keys in the ring.
func (ring *Ring) Len() int {
return len(ring.keys)
}
// Add adds keys to the ring.
func (ring *Ring) Add(keys ...string) {
for _, key := range keys {
for i := 0; i < ring.replicas; i++ {
ring.keys = append(ring.keys, elem{
hash: ring.hashfunc([]byte(strconv.Itoa(i) + key)),
key: key})
}
}
sort.Sort(sortable(ring.keys))
// Calculate signature
hash := fnv.New128a()
b := make([]byte, 4)
for _, key := range ring.keys {
b[0] = byte(key.hash)
b[1] = byte(key.hash >> 8)
b[2] = byte(key.hash >> 16)
b[3] = byte(key.hash >> 24)
hash.Write(b)
hash.Write([]byte(key.key))
}
b = []byte{}
b = hash.Sum(b)
dst := make([]byte, ascii85.MaxEncodedLen(len(b)))
ascii85.Encode(dst, b)
ring.signature = string(dst)
}
// Get returns the closest item in the ring to the provided key.
func (ring *Ring) Get(key string) string {
if ring.Len() == 0 {
return ""
}
hash := ring.hashfunc([]byte(key))
// Binary search for appropriate replica.
idx := sort.Search(len(ring.keys), func(i int) bool {
el := ring.keys[i]
return (el.hash > hash) || (el.hash == hash && el.key >= key)
})
// Means we have cycled back to the first replica.
if idx == len(ring.keys) {
idx = 0
}
return ring.keys[idx].key
}
// Signature returns the ring's hash signature. Two identical ringhashes
// will have the same signature. Two hashes with different
// number of keys or replicas or hash functions will have different
// signatures.
func (ring *Ring) Signature() string {
return ring.signature
}
func (ring *Ring) dump() {
for _, e := range ring.keys {
log.Printf("key: '%s', hash=%d", e.key, e.hash)
}
}