Skip to content
This repository was archived by the owner on Feb 1, 2019. It is now read-only.

Commit e5d4b47

Browse files
authored
Work around Hashing and tests (#2)
* Work around Hashing and tests * fixes * add TravisCI * fix lint * so slow * add badges * work on README * Improved tests and get rid of fnv (#3)
1 parent cf9a7e5 commit e5d4b47

File tree

6 files changed

+373
-158
lines changed

6 files changed

+373
-158
lines changed

.travis.yml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
language: go
2+
go:
3+
- 1.11.x
4+
env:
5+
- GO111MODULE=on
6+
install:
7+
- go get -v golang.org/x/lint/golint
8+
- go mod tidy -v
9+
script:
10+
- golint -set_exit_status ./...
11+
- go test -race -coverprofile=coverage.txt -covermode=atomic ./...
12+
after_success:
13+
- bash <(curl -s https://codecov.io/bash)
14+
matrix:
15+
allow_failures:
16+
- go: tip

README.md

+56-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,56 @@
1-
# Golang simple HRW implementation
1+
# Golang HRW implementation
2+
3+
[![Build Status](https://travis-ci.org/im-kulikov/hrw.svg?branch=master)](https://travis-ci.org/im-kulikov/hrw)
4+
[![codecov](https://codecov.io/gh/im-kulikov/hrw/badge.svg)](https://codecov.io/gh/im-kulikov/hrw)
5+
[![Report](https://goreportcard.com/badge/github.com/im-kulikov/hrw)](https://goreportcard.com/report/github.com/im-kulikov/hrw)
6+
[![GitHub release](https://img.shields.io/github/release/im-kulikov/hrw.svg)](https://github.com/im-kulikov/hrw)
7+
8+
[Rendezvous or highest random weight](https://en.wikipedia.org/wiki/Rendezvous_hashing) (HRW) hashing is an algorithm that allows clients to achieve distributed agreement on a set of k options out of a possible set of n options. A typical application is when clients need to agree on which sites (or proxies) objects are assigned to. When k is 1, it subsumes the goals of consistent hashing, using an entirely different method.
9+
10+
## Install
11+
12+
`go get github.com/im-kulikov/hrw`
13+
14+
## Example
15+
16+
```go
17+
package main
18+
19+
import (
20+
"fmt"
21+
22+
"github.com/im-kulikov/hrw"
23+
)
24+
25+
func main() {
26+
// given a set of servers
27+
servers := []string{
28+
"one.example.com",
29+
"two.example.com",
30+
"three.example.com",
31+
"four.example.com",
32+
"five.example.com",
33+
"six.example.com",
34+
}
35+
36+
// HRW can consistently select a uniformly-distributed set of servers for
37+
// any given key
38+
var (
39+
key = []byte("/examples/object-key")
40+
h = hrw.Hash(key)
41+
)
42+
43+
hrw.SortSliceByValue(servers, h)
44+
for id := range servers {
45+
fmt.Printf("trying GET %s%s\n", servers[id], key)
46+
}
47+
48+
// Output:
49+
// trying GET four.example.com/examples/object-key
50+
// trying GET three.example.com/examples/object-key
51+
// trying GET one.example.com/examples/object-key
52+
// trying GET two.example.com/examples/object-key
53+
// trying GET six.example.com/examples/object-key
54+
// trying GET five.example.com/examples/object-key
55+
}
56+
```

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module github.com/im-kulikov/hrw
22

3-
require github.com/reusee/mmh3 v0.0.0-20140820141314-64b85163255b
3+
require github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
github.com/reusee/mmh3 v0.0.0-20140820141314-64b85163255b h1:GQkEnyBFqzQXb3RFqGt5z2QcBZJVQxgzXKF/sPCFh7w=
2-
github.com/reusee/mmh3 v0.0.0-20140820141314-64b85163255b/go.mod h1:ADBBIMrt68BC/v967NyoiPZMwPVq44r8QJ5oRyXJHJs=
1+
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
2+
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=

hrw.go

+38-27
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@
33
package hrw
44

55
import (
6-
"errors"
7-
"hash/fnv"
6+
"encoding/binary"
87
"reflect"
98
"sort"
10-
"strconv"
9+
10+
"github.com/spaolacci/murmur3"
1111
)
1212

1313
type (
1414
swapper func(i, j int)
1515

16+
// Hasher interface used by SortSliceByValue
1617
Hasher interface{ Hash() uint64 }
1718

1819
hashed struct {
@@ -38,6 +39,12 @@ func (h hashed) Len() int { return h.length }
3839
func (h hashed) Less(i, j int) bool { return h.weight[h.sorted[i]] < h.weight[h.sorted[j]] }
3940
func (h hashed) Swap(i, j int) { h.sorted[i], h.sorted[j] = h.sorted[j], h.sorted[i] }
4041

42+
// Hash uses murmur3 hash to return uint64
43+
func Hash(key []byte) uint64 {
44+
return murmur3.Sum64(key)
45+
}
46+
47+
// SortByWeight receive nodes and hash, and sort it by weight
4148
func SortByWeight(nodes []uint64, hash uint64) []uint64 {
4249
var (
4350
l = len(nodes)
@@ -57,10 +64,11 @@ func SortByWeight(nodes []uint64, hash uint64) []uint64 {
5764
return h.sorted
5865
}
5966

60-
func SortSliceByValue(slice interface{}, hash uint64) error {
67+
// SortSliceByValue received []T and hash to sort by value-weight
68+
func SortSliceByValue(slice interface{}, hash uint64) {
6169
t := reflect.TypeOf(slice)
6270
if t.Kind() != reflect.Slice {
63-
return errors.New("must be slice")
71+
return
6472
}
6573

6674
var (
@@ -71,29 +79,24 @@ func SortSliceByValue(slice interface{}, hash uint64) error {
7179
)
7280

7381
if length == 0 {
74-
return nil
82+
return
7583
}
7684

7785
switch slice := slice.(type) {
7886
case []int:
79-
hasher := fnv.New64()
87+
var key = make([]byte, 16)
8088
for i := 0; i < length; i++ {
81-
hasher.Reset()
82-
// error always nil
83-
_, _ = hasher.Write([]byte(strconv.Itoa(slice[i])))
84-
rule = append(rule, weight(hash, hasher.Sum64()))
89+
binary.BigEndian.PutUint64(key, uint64(slice[i]))
90+
rule = append(rule, weight(Hash(key), hash))
8591
}
8692
case []string:
87-
hasher := fnv.New64()
8893
for i := 0; i < length; i++ {
89-
hasher.Reset()
90-
// error always nil
91-
_, _ = hasher.Write([]byte(slice[i]))
92-
rule = append(rule, weight(hash, hasher.Sum64()))
94+
rule = append(rule, weight(hash,
95+
Hash([]byte(slice[i]))))
9396
}
9497
default:
9598
if _, ok := val.Index(0).Interface().(Hasher); !ok {
96-
return errors.New("unknown type")
99+
return
97100
}
98101

99102
for i := 0; i < length; i++ {
@@ -103,36 +106,44 @@ func SortSliceByValue(slice interface{}, hash uint64) error {
103106
}
104107

105108
rule = SortByWeight(rule, hash)
106-
sortByRule(swap, uint64(length), rule)
107-
108-
return nil
109+
sortByRuleInverse(swap, uint64(length), rule)
109110
}
110111

112+
// SortSliceByIndex received []T and hash to sort by index-weight
111113
func SortSliceByIndex(slice interface{}, hash uint64) {
112114
length := uint64(reflect.ValueOf(slice).Len())
113115
swap := reflect.Swapper(slice)
114-
115116
rule := make([]uint64, 0, length)
116117
for i := uint64(0); i < length; i++ {
117118
rule = append(rule, i)
118119
}
119-
120120
rule = SortByWeight(rule, hash)
121-
sortByRule(swap, length, rule)
121+
sortByRuleInverse(swap, length, rule)
122122
}
123123

124-
func sortByRule(swap swapper, length uint64, rule []uint64) {
124+
func sortByRuleDirect(swap swapper, length uint64, rule []uint64) {
125125
done := make([]bool, length)
126126
for i := uint64(0); i < length; i++ {
127127
if done[i] {
128128
continue
129129
}
130-
131-
done[i] = true
132-
133130
for j := rule[i]; !done[rule[j]]; j = rule[j] {
134131
swap(int(i), int(j))
135132
done[j] = true
136133
}
137134
}
138135
}
136+
137+
func sortByRuleInverse(swap swapper, length uint64, rule []uint64) {
138+
done := make([]bool, length)
139+
for i := uint64(0); i < length; i++ {
140+
if done[i] {
141+
continue
142+
}
143+
144+
for j := i; !done[rule[j]]; j = rule[j] {
145+
swap(int(j), int(rule[j]))
146+
done[j] = true
147+
}
148+
}
149+
}

0 commit comments

Comments
 (0)