chore: sync project files

This commit is contained in:
richjohnrambo
2026-08-11 10:29:38 +08:00
parent c9d6a6767f
commit 5aa7733515
310 changed files with 3323 additions and 36 deletions
+134
View File
@@ -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)
}
}