ingestor/cluster/rendezvous.go (69 lines of code) (raw):
// Package cluster implements rendezvous hashing (a.k.a. highest random
// weight hashing). See http://en.wikipedia.org/wiki/Rendezvous_hashing for
// more information.
// This is based off of https://github.com/tysonmote/rendezvous
package cluster
import (
"bytes"
"hash"
"hash/crc32"
"sort"
"github.com/cespare/xxhash"
)
var crc32Table = crc32.MakeTable(crc32.Castagnoli)
type Hash struct {
nodes nodeScores
hasher hash.Hash32
}
type nodeScore struct {
node []byte
score uint64
}
// New returns a new Hash ready for use with the given nodes.
func NewRendezvous(nodes ...string) *Hash {
hash := &Hash{
hasher: crc32.New(crc32Table),
}
hash.Add(nodes...)
return hash
}
// Add adds additional nodes to the Hash.
func (h *Hash) Add(nodes ...string) {
for _, node := range nodes {
h.nodes = append(h.nodes, nodeScore{node: []byte(node)})
}
}
// Get returns the node with the highest score for the given key. If this Hash
// has no nodes, an empty string is returned.
func (h *Hash) Get(key string) string {
var maxScore uint64
var maxNode []byte
keyBytes := []byte(key)
for _, node := range h.nodes {
score := h.hash(node.node, keyBytes)
if score > maxScore || (score == maxScore && bytes.Compare(node.node, maxNode) < 0) {
maxScore = score
maxNode = node.node
}
}
return string(maxNode)
}
// GetN returns no more than n nodes for the given key, ordered by descending
// score. GetN is not goroutine-safe.
func (h *Hash) GetN(n int, key string) []string {
keyBytes := []byte(key)
for i := 0; i < len(h.nodes); i++ {
h.nodes[i].score = h.hash(h.nodes[i].node, keyBytes)
}
sort.Sort(h.nodes)
if n > len(h.nodes) {
n = len(h.nodes)
}
nodes := make([]string, n)
for i := range nodes {
nodes[i] = string(h.nodes[i].node)
}
return nodes
}
type nodeScores []nodeScore
func (s nodeScores) Len() int { return len(s) }
func (s nodeScores) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s nodeScores) Less(i, j int) bool {
if s[i].score == s[j].score {
return bytes.Compare(s[i].node, s[j].node) < 0
}
return s[j].score < s[i].score // Descending
}
func (h *Hash) hash(node, key []byte) uint64 {
return xxhash.Sum64(append(key, node...))
}