Repository: serialx/hashring
Branch: master
Commit: 22c0c7ab6b1b
Files: 10
Total size: 26.8 KB
Directory structure:
gitextract_o_woei36/
├── .gitignore
├── LICENSE
├── README.md
├── allnodes_test.go
├── benchmark_test.go
├── example_test.go
├── hash.go
├── hashring.go
├── hashring_test.go
└── key.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
coverage
*.html
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2016 Sung-jin Hong
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
hashring
============================
Implements consistent hashing that can be used when
the number of server nodes can increase or decrease (like in memcached).
The hashing ring is built using the same algorithm as libketama.
This is a port of Python hash_ring library <https://pypi.python.org/pypi/hash_ring/>
in Go with the extra methods to add and remove nodes.
Using
============================
Importing ::
```go
import "github.com/serialx/hashring"
```
Basic example usage ::
```go
memcacheServers := []string{"192.168.0.246:11212",
"192.168.0.247:11212",
"192.168.0.249:11212"}
ring := hashring.New(memcacheServers)
server, _ := ring.GetNode("my_key")
```
To fulfill replication requirements, you can also get a list of servers that should store your key.
```go
serversInRing := []string{"192.168.0.246:11212",
"192.168.0.247:11212",
"192.168.0.248:11212",
"192.168.0.249:11212",
"192.168.0.250:11212",
"192.168.0.251:11212",
"192.168.0.252:11212"}
replicaCount := 3
ring := hashring.New(serversInRing)
server, _ := ring.GetNodes("my_key", replicaCount)
```
Using weights example ::
```go
weights := make(map[string]int)
weights["192.168.0.246:11212"] = 1
weights["192.168.0.247:11212"] = 2
weights["192.168.0.249:11212"] = 1
ring := hashring.NewWithWeights(weights)
server, _ := ring.GetNode("my_key")
```
Adding and removing nodes example ::
```go
memcacheServers := []string{"192.168.0.246:11212",
"192.168.0.247:11212",
"192.168.0.249:11212"}
ring := hashring.New(memcacheServers)
ring = ring.RemoveNode("192.168.0.246:11212")
ring = ring.AddNode("192.168.0.250:11212")
server, _ := ring.GetNode("my_key")
```
================================================
FILE: allnodes_test.go
================================================
package hashring
import (
"fmt"
"sort"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
)
func generateWeights(n int) map[string]int {
result := make(map[string]int)
for i := 0; i < n; i++ {
result[fmt.Sprintf("%03d", i)] = i + 1
}
return result
}
func generateNodes(n int) []string {
result := make([]string, 0, n)
for i := 0; i < n; i++ {
result = append(result, fmt.Sprintf("%03d", i))
}
return result
}
func TestListOf1000Nodes(t *testing.T) {
testData := map[string]struct {
ring *HashRing
}{
"nodes": {ring: New(generateNodes(1000))},
"weights": {ring: NewWithWeights(generateWeights(1000))},
}
for testName, data := range testData {
ring := data.ring
t.Run(testName, func(t *testing.T) {
nodes, ok := ring.GetNodes("key", ring.Size())
assert.True(t, ok)
if !assert.Equal(t, ring.Size(), len(nodes)) {
// print debug info on failure
sort.Strings(nodes)
fmt.Printf("%v\n", nodes)
return
}
// assert that each node shows up exatly once
sort.Strings(nodes)
for i, node := range nodes {
actual, err := strconv.ParseInt(node, 10, 64)
if !assert.NoError(t, err) {
return
}
if !assert.Equal(t, int64(i), actual) {
return
}
}
})
}
}
================================================
FILE: benchmark_test.go
================================================
package hashring
import "testing"
func BenchmarkNew(b *testing.B) {
nodes := []string{"a", "b", "c", "d", "e", "f", "g"}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = New(nodes)
}
}
func BenchmarkHashes(b *testing.B) {
nodes := []string{"a", "b", "c", "d", "e", "f", "g"}
ring := New(nodes)
tt := []struct {
key string
nodes []string
}{
{"test", []string{"a", "b"}},
{"test", []string{"a", "b"}},
{"test1", []string{"b", "d"}},
{"test2", []string{"f", "b"}},
{"test3", []string{"f", "c"}},
{"test4", []string{"c", "b"}},
{"test5", []string{"f", "a"}},
{"aaaa", []string{"b", "a"}},
{"bbbb", []string{"f", "a"}},
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
o := tt[i%len(tt)]
ring.GetNodes(o.key, 2)
}
}
func BenchmarkHashesSingle(b *testing.B) {
nodes := []string{"a", "b", "c", "d", "e", "f", "g"}
ring := New(nodes)
tt := []struct {
key string
nodes []string
}{
{"test", []string{"a", "b"}},
{"test", []string{"a", "b"}},
{"test1", []string{"b", "d"}},
{"test2", []string{"f", "b"}},
{"test3", []string{"f", "c"}},
{"test4", []string{"c", "b"}},
{"test5", []string{"f", "a"}},
{"aaaa", []string{"b", "a"}},
{"bbbb", []string{"f", "a"}},
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
o := tt[i%len(tt)]
ring.GetNode(o.key)
}
}
================================================
FILE: example_test.go
================================================
package hashring
import (
"crypto/sha1"
"fmt"
)
func ExampleGetAllNodes() {
hashRing := New([]string{"node1", "node2", "node3"})
nodes, _ := hashRing.GetNodes("key", hashRing.Size())
fmt.Printf("%v", nodes)
// Output: [node3 node2 node1]
}
func ExampleCustomHashError() {
_, err := NewHash(sha1.New).Use(NewInt64PairHashKey)
fmt.Printf("%s", err.Error())
// Output: can't use given hash.Hash with given hashKeyFunc: expected 16 bytes, got 20 bytes
}
func ExampleCustomHash() {
hashFunc, _ := NewHash(sha1.New).FirstBytes(16).Use(NewInt64PairHashKey)
hashRing := NewWithHash([]string{"node1", "node2", "node3"}, hashFunc)
nodes, _ := hashRing.GetNodes("key", hashRing.Size())
fmt.Printf("%v", nodes)
// Output: [node2 node1 node3]
}
func ExampleNewHashFunc() {
hashFunc, _ := NewHash(sha1.New).FirstBytes(16).Use(NewInt64PairHashKey)
fmt.Printf("%v\n", hashFunc([]byte("test")))
// Output: &{-6441359348440544599 -8653224871661646820}
}
================================================
FILE: hash.go
================================================
package hashring
import (
"fmt"
"hash"
)
// HashSum allows to use a builder pattern to create different HashFunc objects.
// See examples for details.
type HashSum struct {
functions []func([]byte) []byte
}
func (r *HashSum) Use(
hashKeyFunc func(bytes []byte) (HashKey, error),
) (HashFunc, error) {
// build final hash function
composed := func(bytes []byte) []byte {
for _, f := range r.functions {
bytes = f(bytes)
}
return bytes
}
// check function composition for errors
testResult := composed([]byte("test"))
_, err := hashKeyFunc(testResult)
if err != nil {
const msg = "can't use given hash.Hash with given hashKeyFunc"
return nil, fmt.Errorf("%s: %w", msg, err)
}
// build HashFunc
return func(key []byte) HashKey {
bytes := composed(key)
hashKey, err := hashKeyFunc(bytes)
if err != nil {
// panic because we already checked HashSum earlier
panic(fmt.Sprintf("hashKeyFunc failure: %v", err))
}
return hashKey
}, nil
}
// NewHash creates a new *HashSum object which can be used to create HashFunc.
// HashFunc object is thread safe if the hasher argument produces a new hash.Hash
// each time. The produced hash.Hash is allowed to be non thread-safe.
func NewHash(hasher func() hash.Hash) *HashSum {
return &HashSum{
functions: []func(key []byte) []byte{
func(key []byte) []byte {
hash := hasher()
hash.Write(key)
return hash.Sum(nil)
},
},
}
}
func (r *HashSum) FirstBytes(n int) *HashSum {
r.functions = append(r.functions, func(bytes []byte) []byte {
return bytes[:n]
})
return r
}
func (r *HashSum) LastBytes(n int) *HashSum {
r.functions = append(r.functions, func(bytes []byte) []byte {
return bytes[len(bytes)-n:]
})
return r
}
================================================
FILE: hashring.go
================================================
package hashring
import (
"crypto/md5"
"fmt"
"sort"
"strconv"
)
var defaultHashFunc = func() HashFunc {
hashFunc, err := NewHash(md5.New).Use(NewInt64PairHashKey)
if err != nil {
panic(fmt.Sprintf("failed to create defaultHashFunc: %s", err.Error()))
}
return hashFunc
}()
type HashKey interface {
Less(other HashKey) bool
}
type HashKeyOrder []HashKey
func (h HashKeyOrder) Len() int { return len(h) }
func (h HashKeyOrder) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h HashKeyOrder) Less(i, j int) bool {
return h[i].Less(h[j])
}
type HashFunc func([]byte) HashKey
type HashRing struct {
ring map[HashKey]string
sortedKeys []HashKey
nodes []string
weights map[string]int
hashFunc HashFunc
}
type Uint32HashKey uint32
func (k Uint32HashKey) Less(other HashKey) bool {
return k < other.(Uint32HashKey)
}
func New(nodes []string) *HashRing {
return NewWithHash(nodes, defaultHashFunc)
}
func NewWithHash(
nodes []string,
hashKey HashFunc,
) *HashRing {
hashRing := &HashRing{
ring: make(map[HashKey]string),
sortedKeys: make([]HashKey, 0),
nodes: nodes,
weights: make(map[string]int),
hashFunc: hashKey,
}
hashRing.generateCircle()
return hashRing
}
func NewWithWeights(weights map[string]int) *HashRing {
return NewWithHashAndWeights(weights, defaultHashFunc)
}
func NewWithHashAndWeights(
weights map[string]int,
hashFunc HashFunc,
) *HashRing {
nodes := make([]string, 0, len(weights))
for node := range weights {
nodes = append(nodes, node)
}
hashRing := &HashRing{
ring: make(map[HashKey]string),
sortedKeys: make([]HashKey, 0),
nodes: nodes,
weights: weights,
hashFunc: hashFunc,
}
hashRing.generateCircle()
return hashRing
}
func (h *HashRing) Size() int {
return len(h.nodes)
}
func (h *HashRing) UpdateWithWeights(weights map[string]int) {
nodesChgFlg := false
if len(weights) != len(h.weights) {
nodesChgFlg = true
} else {
for node, newWeight := range weights {
oldWeight, ok := h.weights[node]
if !ok || oldWeight != newWeight {
nodesChgFlg = true
break
}
}
}
if nodesChgFlg {
newhring := NewWithHashAndWeights(weights, h.hashFunc)
h.weights = newhring.weights
h.nodes = newhring.nodes
h.ring = newhring.ring
h.sortedKeys = newhring.sortedKeys
}
}
func (h *HashRing) generateCircle() {
totalWeight := 0
for _, node := range h.nodes {
if weight, ok := h.weights[node]; ok {
totalWeight += weight
} else {
totalWeight += 1
h.weights[node] = 1
}
}
for _, node := range h.nodes {
weight := h.weights[node]
for j := 0; j < weight; j++ {
nodeKey := node + "-" + strconv.FormatInt(int64(j), 10)
key := h.hashFunc([]byte(nodeKey))
h.ring[key] = node
h.sortedKeys = append(h.sortedKeys, key)
}
}
sort.Sort(HashKeyOrder(h.sortedKeys))
}
func (h *HashRing) GetNode(stringKey string) (node string, ok bool) {
pos, ok := h.GetNodePos(stringKey)
if !ok {
return "", false
}
return h.ring[h.sortedKeys[pos]], true
}
func (h *HashRing) GetNodePos(stringKey string) (pos int, ok bool) {
if len(h.ring) == 0 {
return 0, false
}
key := h.GenKey(stringKey)
nodes := h.sortedKeys
pos = sort.Search(len(nodes), func(i int) bool { return key.Less(nodes[i]) })
if pos == len(nodes) {
// Wrap the search, should return First node
return 0, true
} else {
return pos, true
}
}
func (h *HashRing) GenKey(key string) HashKey {
return h.hashFunc([]byte(key))
}
// GetNodes iterates over the hash ring and returns the nodes in the order
// which is determined by the key. GetNodes is thread safe if the hash
// which was used to configure the hash ring is thread safe.
func (h *HashRing) GetNodes(stringKey string, size int) (nodes []string, ok bool) {
pos, ok := h.GetNodePos(stringKey)
if !ok {
return nil, false
}
if size > len(h.nodes) {
return nil, false
}
returnedValues := make(map[string]bool, size)
//mergedSortedKeys := append(h.sortedKeys[pos:], h.sortedKeys[:pos]...)
resultSlice := make([]string, 0, size)
for i := pos; i < pos+len(h.sortedKeys); i++ {
key := h.sortedKeys[i%len(h.sortedKeys)]
val := h.ring[key]
if !returnedValues[val] {
returnedValues[val] = true
resultSlice = append(resultSlice, val)
}
if len(returnedValues) == size {
break
}
}
return resultSlice, len(resultSlice) == size
}
func (h *HashRing) AddNode(node string) *HashRing {
return h.AddWeightedNode(node, 1)
}
func (h *HashRing) AddWeightedNode(node string, weight int) *HashRing {
if weight <= 0 {
return h
}
if _, ok := h.weights[node]; ok {
return h
}
nodes := make([]string, len(h.nodes), len(h.nodes)+1)
copy(nodes, h.nodes)
nodes = append(nodes, node)
weights := make(map[string]int)
for eNode, eWeight := range h.weights {
weights[eNode] = eWeight
}
weights[node] = weight
hashRing := &HashRing{
ring: make(map[HashKey]string),
sortedKeys: make([]HashKey, 0),
nodes: nodes,
weights: weights,
hashFunc: h.hashFunc,
}
hashRing.generateCircle()
return hashRing
}
func (h *HashRing) UpdateWeightedNode(node string, weight int) *HashRing {
if weight <= 0 {
return h
}
/* node is not need to update for node is not existed or weight is not changed */
if oldWeight, ok := h.weights[node]; (!ok) || (ok && oldWeight == weight) {
return h
}
nodes := make([]string, len(h.nodes))
copy(nodes, h.nodes)
weights := make(map[string]int)
for eNode, eWeight := range h.weights {
weights[eNode] = eWeight
}
weights[node] = weight
hashRing := &HashRing{
ring: make(map[HashKey]string),
sortedKeys: make([]HashKey, 0),
nodes: nodes,
weights: weights,
hashFunc: h.hashFunc,
}
hashRing.generateCircle()
return hashRing
}
func (h *HashRing) RemoveNode(node string) *HashRing {
/* if node isn't exist in hashring, don't refresh hashring */
if _, ok := h.weights[node]; !ok {
return h
}
nodes := make([]string, 0)
for _, eNode := range h.nodes {
if eNode != node {
nodes = append(nodes, eNode)
}
}
weights := make(map[string]int)
for eNode, eWeight := range h.weights {
if eNode != node {
weights[eNode] = eWeight
}
}
hashRing := &HashRing{
ring: make(map[HashKey]string),
sortedKeys: make([]HashKey, 0),
nodes: nodes,
weights: weights,
hashFunc: h.hashFunc,
}
hashRing.generateCircle()
return hashRing
}
================================================
FILE: hashring_test.go
================================================
package hashring
import (
"fmt"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
)
func expectWeights(t *testing.T, ring *HashRing, expectedWeights map[string]int) {
weightsEquality := reflect.DeepEqual(ring.weights, expectedWeights)
if !weightsEquality {
t.Error("Weights expected", expectedWeights, "but got", ring.weights)
}
}
type testPair struct {
key string
node string
}
type testNodes struct {
key string
nodes []string
}
func assert2Nodes(t *testing.T, prefix string, ring *HashRing, data []testNodes) {
t.Run(prefix, func(t *testing.T) {
allActual := make([]string, 0)
allExpected := make([]string, 0)
for _, pair := range data {
nodes, ok := ring.GetNodes(pair.key, 2)
if assert.True(t, ok) {
allActual = append(allActual, fmt.Sprintf("%s - %v", pair.key, nodes))
allExpected = append(allExpected, fmt.Sprintf("%s - %v", pair.key, pair.nodes))
}
}
assert.Equal(t, allExpected, allActual)
})
}
func assertNodes(t *testing.T, prefix string, ring *HashRing, allExpected []testPair) {
t.Run(prefix, func(t *testing.T) {
allActual := make([]testPair, 0)
for _, pair := range allExpected {
node, ok := ring.GetNode(pair.key)
if assert.True(t, ok) {
allActual = append(allActual, testPair{key: pair.key, node: node})
}
}
})
}
func expectNodesABC(t *testing.T, prefix string, ring *HashRing) {
assertNodes(t, prefix, ring, []testPair{
{"test", "a"},
{"test", "a"},
{"test1", "b"},
{"test2", "b"},
{"test3", "c"},
{"test4", "a"},
{"test5", "c"},
{"aaaa", "c"},
{"bbbb", "a"},
})
}
func expectNodeRangesABC(t *testing.T, prefix string, ring *HashRing) {
assert2Nodes(t, prefix, ring, []testNodes{
{"test", []string{"a", "c"}},
{"test", []string{"a", "c"}},
{"test1", []string{"b", "a"}},
{"test2", []string{"b", "a"}},
{"test3", []string{"c", "b"}},
{"test4", []string{"a", "c"}},
{"test5", []string{"c", "b"}},
{"aaaa", []string{"c", "b"}},
{"bbbb", []string{"a", "c"}},
})
}
func expectNodesABCD(t *testing.T, prefix string, ring *HashRing) {
assertNodes(t, prefix, ring, []testPair{
{"test", "d"},
{"test", "d"},
{"test1", "b"},
{"test2", "b"},
{"test3", "c"},
{"test4", "d"},
{"test5", "c"},
{"aaaa", "c"},
{"bbbb", "d"},
})
}
func TestNew(t *testing.T) {
nodes := []string{"a", "b", "c"}
ring := New(nodes)
expectNodesABC(t, "TestNew_1_", ring)
expectNodeRangesABC(t, "", ring)
}
func TestNewEmpty(t *testing.T) {
nodes := []string{}
ring := New(nodes)
node, ok := ring.GetNode("test")
if ok || node != "" {
t.Error("GetNode(test) expected (\"\", false) but got (", node, ",", ok, ")")
}
nodes, rok := ring.GetNodes("test", 2)
if rok || !(len(nodes) == 0) {
t.Error("GetNode(test) expected ( [], false ) but got (", nodes, ",", rok, ")")
}
}
func TestForMoreNodes(t *testing.T) {
nodes := []string{"a", "b", "c"}
ring := New(nodes)
nodes, ok := ring.GetNodes("test", 5)
if ok || !(len(nodes) == 0) {
t.Error("GetNode(test) expected ( [], false ) but got (", nodes, ",", ok, ")")
}
}
func TestForEqualNodes(t *testing.T) {
nodes := []string{"a", "b", "c"}
ring := New(nodes)
nodes, ok := ring.GetNodes("test", 3)
if !ok && (len(nodes) == 3) {
t.Error("GetNode(test) expected ( [a b c], true ) but got (", nodes, ",", ok, ")")
}
}
func TestNewSingle(t *testing.T) {
nodes := []string{"a"}
ring := New(nodes)
assertNodes(t, "", ring, []testPair{
{"test", "a"},
{"test", "a"},
{"test1", "a"},
{"test2", "a"},
{"test3", "a"},
{"test14", "a"},
{"test15", "a"},
{"test16", "a"},
{"test17", "a"},
{"test18", "a"},
{"test19", "a"},
{"test20", "a"},
})
}
func TestNewWeighted(t *testing.T) {
weights := make(map[string]int)
weights["a"] = 1
weights["b"] = 2
weights["c"] = 1
ring := NewWithWeights(weights)
assertNodes(t, "", ring, []testPair{
{"test", "b"},
{"test", "b"},
{"test1", "b"},
{"test2", "b"},
{"test3", "c"},
{"test4", "b"},
{"test5", "c"},
{"aaaa", "c"},
{"bbbb", "b"},
})
assert2Nodes(t, "", ring, []testNodes{
{"test", []string{"b", "a"}},
})
}
func TestRemoveNode(t *testing.T) {
nodes := []string{"a", "b", "c"}
ring := New(nodes)
ring = ring.RemoveNode("b")
assertNodes(t, "", ring, []testPair{
{"test", "a"},
{"test", "a"},
{"test1", "a"},
{"test2", "a"},
{"test3", "c"},
{"test4", "a"},
{"test5", "c"},
{"aaaa", "c"},
{"bbbb", "a"},
})
assert2Nodes(t, "", ring, []testNodes{
{"test", []string{"a", "c"}},
})
}
func TestAddNode(t *testing.T) {
nodes := []string{"a", "c"}
ring := New(nodes)
ring = ring.AddNode("b")
expectNodesABC(t, "TestAddNode_1_", ring)
defaultWeights := map[string]int{
"a": 1,
"b": 1,
"c": 1,
}
expectWeights(t, ring, defaultWeights)
}
func TestAddNode2(t *testing.T) {
nodes := []string{"a", "c"}
ring := New(nodes)
ring = ring.AddNode("b")
ring = ring.AddNode("b")
expectNodesABC(t, "TestAddNode2_", ring)
expectNodeRangesABC(t, "", ring)
}
func TestAddNode3(t *testing.T) {
nodes := []string{"a", "b", "c"}
ring := New(nodes)
ring = ring.AddNode("d")
expectNodesABCD(t, "TestAddNode3_1_", ring)
ring = ring.AddNode("e")
assertNodes(t, "TestAddNode3_2_", ring, []testPair{
{"test", "d"},
{"test", "d"},
{"test1", "b"},
{"test2", "e"},
{"test3", "c"},
{"test4", "d"},
{"test5", "c"},
{"aaaa", "c"},
{"bbbb", "d"},
})
assert2Nodes(t, "", ring, []testNodes{
{"test", []string{"d", "a"}},
})
ring = ring.AddNode("f")
assertNodes(t, "TestAddNode3_3_", ring, []testPair{
{"test", "d"},
{"test", "d"},
{"test1", "b"},
{"test2", "e"},
{"test3", "c"},
{"test4", "d"},
{"test5", "c"},
{"aaaa", "c"},
{"bbbb", "d"},
})
assert2Nodes(t, "", ring, []testNodes{
{"test", []string{"d", "a"}},
})
}
func TestDuplicateNodes(t *testing.T) {
nodes := []string{"a", "a", "a", "a", "b"}
ring := New(nodes)
assertNodes(t, "TestDuplicateNodes_", ring, []testPair{
{"test", "a"},
{"test", "a"},
{"test1", "b"},
{"test2", "b"},
{"test3", "b"},
{"test4", "a"},
{"test5", "b"},
{"aaaa", "b"},
{"bbbb", "a"},
})
}
func TestAddWeightedNode(t *testing.T) {
nodes := []string{"a", "c"}
ring := New(nodes)
ring = ring.AddWeightedNode("b", 0)
ring = ring.AddWeightedNode("b", 2)
ring = ring.AddWeightedNode("b", 2)
assertNodes(t, "TestAddWeightedNode_", ring, []testPair{
{"test", "b"},
{"test", "b"},
{"test1", "b"},
{"test2", "b"},
{"test3", "c"},
{"test4", "b"},
{"test5", "c"},
{"aaaa", "c"},
{"bbbb", "b"},
})
assert2Nodes(t, "", ring, []testNodes{
{"test", []string{"b", "a"}},
})
}
func TestUpdateWeightedNode(t *testing.T) {
nodes := []string{"a", "c"}
ring := New(nodes)
ring = ring.AddWeightedNode("b", 1)
ring = ring.UpdateWeightedNode("b", 2)
ring = ring.UpdateWeightedNode("b", 2)
ring = ring.UpdateWeightedNode("b", 0)
ring = ring.UpdateWeightedNode("d", 2)
assertNodes(t, "TestUpdateWeightedNode_", ring, []testPair{
{"test", "b"},
{"test", "b"},
{"test1", "b"},
{"test2", "b"},
{"test3", "c"},
{"test4", "b"},
{"test5", "c"},
{"aaaa", "c"},
{"bbbb", "b"},
})
assert2Nodes(t, "", ring, []testNodes{
{"test", []string{"b", "a"}},
})
}
func TestRemoveAddNode(t *testing.T) {
nodes := []string{"a", "b", "c"}
ring := New(nodes)
expectNodesABC(t, "1_", ring)
expectNodeRangesABC(t, "2_", ring)
ring = ring.RemoveNode("b")
assertNodes(t, "3_", ring, []testPair{
{"test", "a"},
{"test", "a"},
{"test1", "a"},
{"test2", "a"},
{"test3", "c"},
{"test4", "a"},
{"test5", "c"},
{"aaaa", "c"},
{"bbbb", "a"},
})
assert2Nodes(t, "4_", ring, []testNodes{
{"test", []string{"a", "c"}},
{"test", []string{"a", "c"}},
{"test1", []string{"a", "c"}},
{"test2", []string{"a", "c"}},
{"test3", []string{"c", "a"}},
{"test4", []string{"a", "c"}},
{"test5", []string{"c", "a"}},
{"aaaa", []string{"c", "a"}},
{"bbbb", []string{"a", "c"}},
})
ring = ring.AddNode("b")
expectNodesABC(t, "5_", ring)
expectNodeRangesABC(t, "6_", ring)
}
func TestRemoveAddWeightedNode(t *testing.T) {
weights := make(map[string]int)
weights["a"] = 1
weights["b"] = 2
weights["c"] = 1
ring := NewWithWeights(weights)
expectWeights(t, ring, weights)
assertNodes(t, "1_", ring, []testPair{
{"test", "b"},
{"test", "b"},
{"test1", "b"},
{"test2", "b"},
{"test3", "c"},
{"test4", "b"},
{"test5", "c"},
{"aaaa", "c"},
{"bbbb", "b"},
})
assert2Nodes(t, "2_", ring, []testNodes{
{"test", []string{"b", "a"}},
{"test", []string{"b", "a"}},
{"test1", []string{"b", "a"}},
{"test2", []string{"b", "a"}},
{"test3", []string{"c", "b"}},
{"test4", []string{"b", "a"}},
{"test5", []string{"c", "b"}},
{"aaaa", []string{"c", "b"}},
{"bbbb", []string{"b", "a"}},
})
ring = ring.RemoveNode("c")
delete(weights, "c")
expectWeights(t, ring, weights)
assertNodes(t, "3_", ring, []testPair{
{"test", "b"},
{"test", "b"},
{"test1", "b"},
{"test2", "b"},
{"test3", "b"},
{"test4", "b"},
{"test5", "b"},
{"aaaa", "b"},
{"bbbb", "b"},
})
assert2Nodes(t, "4_", ring, []testNodes{
{"test", []string{"b", "a"}},
{"test", []string{"b", "a"}},
{"test1", []string{"b", "a"}},
{"test2", []string{"b", "a"}},
{"test3", []string{"b", "a"}},
{"test4", []string{"b", "a"}},
{"test5", []string{"b", "a"}},
{"aaaa", []string{"b", "a"}},
{"bbbb", []string{"b", "a"}},
})
}
func TestAddRemoveNode(t *testing.T) {
nodes := []string{"a", "b", "c"}
ring := New(nodes)
ring = ring.AddNode("d")
expectNodesABCD(t, "1_", ring)
assert2Nodes(t, "2_", ring, []testNodes{
{"test", []string{"d", "a"}},
{"test", []string{"d", "a"}},
{"test1", []string{"b", "d"}},
{"test2", []string{"b", "d"}},
{"test3", []string{"c", "b"}},
{"test4", []string{"d", "a"}},
{"test5", []string{"c", "b"}},
{"aaaa", []string{"c", "b"}},
{"bbbb", []string{"d", "a"}},
})
ring = ring.AddNode("e")
assertNodes(t, "3_", ring, []testPair{
{"test", "a"},
{"test", "a"},
{"test1", "b"},
{"test2", "b"},
{"test3", "c"},
{"test4", "c"},
{"test5", "a"},
{"aaaa", "b"},
{"bbbb", "e"},
})
assert2Nodes(t, "4_", ring, []testNodes{
{"test", []string{"d", "a"}},
{"test", []string{"d", "a"}},
{"test1", []string{"b", "d"}},
{"test2", []string{"e", "b"}},
{"test3", []string{"c", "e"}},
{"test4", []string{"d", "a"}},
{"test5", []string{"c", "e"}},
{"aaaa", []string{"c", "e"}},
{"bbbb", []string{"d", "a"}},
})
ring = ring.AddNode("f")
assertNodes(t, "5_", ring, []testPair{
{"test", "a"},
{"test", "a"},
{"test1", "b"},
{"test2", "f"},
{"test3", "f"},
{"test4", "c"},
{"test5", "f"},
{"aaaa", "b"},
{"bbbb", "e"},
})
assert2Nodes(t, "6_", ring, []testNodes{
{"test", []string{"d", "a"}},
{"test", []string{"d", "a"}},
{"test1", []string{"b", "d"}},
{"test2", []string{"e", "f"}},
{"test3", []string{"c", "e"}},
{"test4", []string{"d", "a"}},
{"test5", []string{"c", "e"}},
{"aaaa", []string{"c", "e"}},
{"bbbb", []string{"d", "a"}},
})
ring = ring.RemoveNode("e")
assertNodes(t, "7_", ring, []testPair{
{"test", "a"},
{"test", "a"},
{"test1", "b"},
{"test2", "f"},
{"test3", "f"},
{"test4", "c"},
{"test5", "f"},
{"aaaa", "b"},
{"bbbb", "f"},
})
assert2Nodes(t, "8_", ring, []testNodes{
{"test", []string{"d", "a"}},
{"test", []string{"d", "a"}},
{"test1", []string{"b", "d"}},
{"test2", []string{"f", "b"}},
{"test3", []string{"c", "f"}},
{"test4", []string{"d", "a"}},
{"test5", []string{"c", "f"}},
{"aaaa", []string{"c", "f"}},
{"bbbb", []string{"d", "a"}},
})
ring = ring.RemoveNode("f")
expectNodesABCD(t, "TestAddRemoveNode_5_", ring)
assert2Nodes(t, "", ring, []testNodes{
{"test", []string{"d", "a"}},
{"test", []string{"d", "a"}},
{"test1", []string{"b", "d"}},
{"test2", []string{"b", "d"}},
{"test3", []string{"c", "b"}},
{"test4", []string{"d", "a"}},
{"test5", []string{"c", "b"}},
{"aaaa", []string{"c", "b"}},
{"bbbb", []string{"d", "a"}},
})
ring = ring.RemoveNode("d")
expectNodesABC(t, "TestAddRemoveNode_6_", ring)
expectNodeRangesABC(t, "", ring)
}
================================================
FILE: key.go
================================================
package hashring
import (
"encoding/binary"
"fmt"
)
type Int64PairHashKey struct {
High int64
Low int64
}
func (k *Int64PairHashKey) Less(other HashKey) bool {
o := other.(*Int64PairHashKey)
if k.High < o.High {
return true
}
return k.High == o.High && k.Low < o.Low
}
func NewInt64PairHashKey(bytes []byte) (HashKey, error) {
const expected = 16
if len(bytes) != expected {
return nil, fmt.Errorf(
"expected %d bytes, got %d bytes",
expected, len(bytes),
)
}
return &Int64PairHashKey{
High: int64(binary.LittleEndian.Uint64(bytes[:8])),
Low: int64(binary.LittleEndian.Uint64(bytes[8:])),
}, nil
}
gitextract_o_woei36/ ├── .gitignore ├── LICENSE ├── README.md ├── allnodes_test.go ├── benchmark_test.go ├── example_test.go ├── hash.go ├── hashring.go ├── hashring_test.go └── key.go
SYMBOL INDEX (66 symbols across 7 files)
FILE: allnodes_test.go
function generateWeights (line 12) | func generateWeights(n int) map[string]int {
function generateNodes (line 20) | func generateNodes(n int) []string {
function TestListOf1000Nodes (line 28) | func TestListOf1000Nodes(t *testing.T) {
FILE: benchmark_test.go
function BenchmarkNew (line 5) | func BenchmarkNew(b *testing.B) {
function BenchmarkHashes (line 13) | func BenchmarkHashes(b *testing.B) {
function BenchmarkHashesSingle (line 37) | func BenchmarkHashesSingle(b *testing.B) {
FILE: example_test.go
function ExampleGetAllNodes (line 8) | func ExampleGetAllNodes() {
function ExampleCustomHashError (line 15) | func ExampleCustomHashError() {
function ExampleCustomHash (line 21) | func ExampleCustomHash() {
function ExampleNewHashFunc (line 29) | func ExampleNewHashFunc() {
FILE: hash.go
type HashSum (line 10) | type HashSum struct
method Use (line 14) | func (r *HashSum) Use(
method FirstBytes (line 61) | func (r *HashSum) FirstBytes(n int) *HashSum {
method LastBytes (line 68) | func (r *HashSum) LastBytes(n int) *HashSum {
function NewHash (line 49) | func NewHash(hasher func() hash.Hash) *HashSum {
FILE: hashring.go
type HashKey (line 18) | type HashKey interface
type HashKeyOrder (line 21) | type HashKeyOrder
method Len (line 23) | func (h HashKeyOrder) Len() int { return len(h) }
method Swap (line 24) | func (h HashKeyOrder) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
method Less (line 25) | func (h HashKeyOrder) Less(i, j int) bool {
type HashFunc (line 29) | type HashFunc
type HashRing (line 31) | type HashRing struct
method Size (line 87) | func (h *HashRing) Size() int {
method UpdateWithWeights (line 91) | func (h *HashRing) UpdateWithWeights(weights map[string]int) {
method generateCircle (line 114) | func (h *HashRing) generateCircle() {
method GetNode (line 139) | func (h *HashRing) GetNode(stringKey string) (node string, ok bool) {
method GetNodePos (line 147) | func (h *HashRing) GetNodePos(stringKey string) (pos int, ok bool) {
method GenKey (line 165) | func (h *HashRing) GenKey(key string) HashKey {
method GetNodes (line 172) | func (h *HashRing) GetNodes(stringKey string, size int) (nodes []strin...
method AddNode (line 201) | func (h *HashRing) AddNode(node string) *HashRing {
method AddWeightedNode (line 205) | func (h *HashRing) AddWeightedNode(node string, weight int) *HashRing {
method UpdateWeightedNode (line 235) | func (h *HashRing) UpdateWeightedNode(node string, weight int) *HashRi...
method RemoveNode (line 264) | func (h *HashRing) RemoveNode(node string) *HashRing {
type Uint32HashKey (line 39) | type Uint32HashKey
method Less (line 41) | func (k Uint32HashKey) Less(other HashKey) bool {
function New (line 45) | func New(nodes []string) *HashRing {
function NewWithHash (line 49) | func NewWithHash(
function NewWithWeights (line 64) | func NewWithWeights(weights map[string]int) *HashRing {
function NewWithHashAndWeights (line 68) | func NewWithHashAndWeights(
FILE: hashring_test.go
function expectWeights (line 11) | func expectWeights(t *testing.T, ring *HashRing, expectedWeights map[str...
type testPair (line 18) | type testPair struct
type testNodes (line 23) | type testNodes struct
function assert2Nodes (line 28) | func assert2Nodes(t *testing.T, prefix string, ring *HashRing, data []te...
function assertNodes (line 43) | func assertNodes(t *testing.T, prefix string, ring *HashRing, allExpecte...
function expectNodesABC (line 55) | func expectNodesABC(t *testing.T, prefix string, ring *HashRing) {
function expectNodeRangesABC (line 70) | func expectNodeRangesABC(t *testing.T, prefix string, ring *HashRing) {
function expectNodesABCD (line 84) | func expectNodesABCD(t *testing.T, prefix string, ring *HashRing) {
function TestNew (line 98) | func TestNew(t *testing.T) {
function TestNewEmpty (line 106) | func TestNewEmpty(t *testing.T) {
function TestForMoreNodes (line 121) | func TestForMoreNodes(t *testing.T) {
function TestForEqualNodes (line 131) | func TestForEqualNodes(t *testing.T) {
function TestNewSingle (line 141) | func TestNewSingle(t *testing.T) {
function TestNewWeighted (line 163) | func TestNewWeighted(t *testing.T) {
function TestRemoveNode (line 186) | func TestRemoveNode(t *testing.T) {
function TestAddNode (line 208) | func TestAddNode(t *testing.T) {
function TestAddNode2 (line 223) | func TestAddNode2(t *testing.T) {
function TestAddNode3 (line 233) | func TestAddNode3(t *testing.T) {
function TestDuplicateNodes (line 277) | func TestDuplicateNodes(t *testing.T) {
function TestAddWeightedNode (line 294) | func TestAddWeightedNode(t *testing.T) {
function TestUpdateWeightedNode (line 318) | func TestUpdateWeightedNode(t *testing.T) {
function TestRemoveAddNode (line 344) | func TestRemoveAddNode(t *testing.T) {
function TestRemoveAddWeightedNode (line 383) | func TestRemoveAddWeightedNode(t *testing.T) {
function TestAddRemoveNode (line 446) | func TestAddRemoveNode(t *testing.T) {
FILE: key.go
type Int64PairHashKey (line 8) | type Int64PairHashKey struct
method Less (line 13) | func (k *Int64PairHashKey) Less(other HashKey) bool {
function NewInt64PairHashKey (line 21) | func NewInt64PairHashKey(bytes []byte) (HashKey, error) {
Condensed preview — 10 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (32K chars).
[
{
"path": ".gitignore",
"chars": 17,
"preview": "coverage\n*.html\n\n"
},
{
"path": "LICENSE",
"chars": 1080,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016 Sung-jin Hong\n\nPermission is hereby granted, free of charge, to any person obt"
},
{
"path": "README.md",
"chars": 1909,
"preview": "hashring\n============================\n\nImplements consistent hashing that can be used when\nthe number of server nodes ca"
},
{
"path": "allnodes_test.go",
"chars": 1252,
"preview": "package hashring\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc generateW"
},
{
"path": "benchmark_test.go",
"chars": 1308,
"preview": "package hashring\n\nimport \"testing\"\n\nfunc BenchmarkNew(b *testing.B) {\n\tnodes := []string{\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \""
},
{
"path": "example_test.go",
"chars": 958,
"preview": "package hashring\n\nimport (\n\t\"crypto/sha1\"\n\t\"fmt\"\n)\n\nfunc ExampleGetAllNodes() {\n\thashRing := New([]string{\"node1\", \"node"
},
{
"path": "hash.go",
"chars": 1733,
"preview": "package hashring\n\nimport (\n\t\"fmt\"\n\t\"hash\"\n)\n\n// HashSum allows to use a builder pattern to create different HashFunc obj"
},
{
"path": "hashring.go",
"chars": 6403,
"preview": "package hashring\n\nimport (\n\t\"crypto/md5\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strconv\"\n)\n\nvar defaultHashFunc = func() HashFunc {\n\thashFunc,"
},
{
"path": "hashring_test.go",
"chars": 12158,
"preview": "package hashring\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc expectWeights(t *"
},
{
"path": "key.go",
"chars": 633,
"preview": "package hashring\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n)\n\ntype Int64PairHashKey struct {\n\tHigh int64\n\tLow int64\n}\n\nfunc ("
}
]
About this extraction
This page contains the full source code of the serialx/hashring GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 10 files (26.8 KB), approximately 9.3k tokens, and a symbol index with 66 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.