chore: sync project files
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
// Package ringhash implementats a consistent ring hash:
|
||||
// https://en.wikipedia.org/wiki/Consistent_hashing
|
||||
package ringhash
|
||||
|
||||
import (
|
||||
"encoding/ascii85"
|
||||
"hash/crc32"
|
||||
"hash/fnv"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/tinode/chat/server/logs"
|
||||
)
|
||||
|
||||
// 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, crc32.NewIEEE() 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 := crc32.NewIEEE()
|
||||
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 := range ring.replicas {
|
||||
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 {
|
||||
logs.Info.Printf("key: '%s', hash=%d", e.key, e.hash)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user