A Go library that implements Consistent Hashing with zero dependencies. This package is based on golang/groupcache with a few additions.
Removefunction added - removes a node (and all of its replicas) from the ring- int hashes replaced with
uint32 - Number of replicas is configurable per node via
AddReplicas(useful when nodes have different capacity) - Concurrency-safe: all operations are guarded by a single
sync.RWMutex - Stored keys are copied, so mutating the caller's buffer afterwards can't corrupt the ring
Note: the
WithBlockPartitioningoption was removed. The earlier block-based lookup returned the wrong node once the ring spanned more than one block and was not concurrency-safe; lookups now use a sorted ring with binary search (O(log N)), which also benchmarked faster. No code change is needed unless you were passingWithBlockPartitioning(simply drop it).
The ring is a single slice of positions kept sorted by hash value, plus two maps:
- ring
[]node- every position on the ring (one per replica), sorted by hash. Each entry points back to the original key. - hashMap
map[uint32][]byte- maps the hash of a key to the original key value. - replicaMap
map[uint32]uint- records the replica count for a key only when it differs from the default (so adding 10k keys at the default replica count uses zero extra entries here).
Get hashes the lookup key and binary-searches the sorted ring for the first
position >= hash, wrapping back to the first position when the key falls past
the end of the ring. Time complexity is O(log N) where N is the total number
of positions (keys x replicas).
Add / AddReplicas generate the replica positions for each key, append them to
the ring, then sort and de-duplicate. Re-adding the same key is idempotent. The
sort makes the result independent of insertion order, so two rings built from the
same keys always agree.
Remove recomputes the exact positions a key occupies and filters them out of the
ring in a single pass. It returns false if the key was not present.
go get github.com/mbrostami/consistenthash/v2
package main
import (
"fmt"
"github.com/mbrostami/consistenthash/v2"
)
func main() {
ch := consistenthash.New(consistenthash.WithDefaultReplicas(10))
ch.Add([]byte("templateA"), []byte("templateB"))
assignedTemplate := ch.Get([]byte("userA")) // assigned template should always be the same for `userA`
fmt.Printf("assigned: %s", assignedTemplate)
}package main
import (
"fmt"
"github.com/mbrostami/consistenthash/v2"
)
func main() {
ch := consistenthash.New(consistenthash.WithDefaultReplicas(10))
ch.Add([]byte("127.0.0.1:1001"), []byte("127.0.0.1:1002"))
ch.AddReplicas(40, []byte("127.0.0.1:1003")) // 4x more weight
rKey := "something like request url or user id"
node := ch.Get([]byte(rKey)) // find upper closest node
fmt.Println(node) // this will print out one of the nodes
}You can find some explanation in this blog post: https://liuzhenglaichn.gitbook.io/system-design/advanced/consistent-hashing
The code example to achieve a hash ring similar to the following picture:
package main
import (
"fmt"
"github.com/mbrostami/consistenthash/v2"
)
func main() {
ch := consistenthash.New(consistenthash.WithDefaultReplicas(3))
ch.Add([]byte("A"), []byte("B"), []byte("C"))
fmt.Println(ch.Get([]byte("Alica")))
fmt.Println(ch.Get([]byte("Bob")))
fmt.Println(ch.Get([]byte("Casey")))
}