You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
110 lines
2.3 KiB
110 lines
2.3 KiB
package deliver |
|
|
|
import ( |
|
"hash/fnv" |
|
"sort" |
|
"strconv" |
|
) |
|
|
|
type PickNode struct { |
|
Key string |
|
Weight int |
|
} |
|
|
|
type ConsistentHashPicker struct { |
|
nodes []*PickNode |
|
replicas int |
|
salt string |
|
hashKeys []uint32 // sorted hashKeys |
|
hashNodes map[uint32]*PickNode |
|
length int |
|
} |
|
|
|
func NewConsistentHashPicker(nodes []*PickNode, replicas int, salt string) *ConsistentHashPicker { |
|
return &ConsistentHashPicker{ |
|
nodes: nodes, |
|
replicas: replicas, |
|
salt: salt, |
|
hashKeys: make([]uint32, 0, len(nodes)*replicas), |
|
hashNodes: make(map[uint32]*PickNode, len(nodes)*replicas), |
|
} |
|
} |
|
|
|
var ( |
|
DefaultReplicas = 10 |
|
DefaultSalt = "this_is_salt" |
|
) |
|
|
|
func (c *ConsistentHashPicker) hashFnv32(data []byte) uint32 { |
|
f := fnv.New32() |
|
_, err := f.Write(data) |
|
if err != nil { |
|
panic(err) |
|
} |
|
return f.Sum32() |
|
} |
|
|
|
// Init 构建hash环 |
|
func (c *ConsistentHashPicker) Init() { |
|
for _, node := range c.nodes { |
|
weight := node.Weight |
|
if weight < 1 { |
|
weight = 1 |
|
} |
|
for i := 0; i < weight; i++ { |
|
for j := 0; j < c.replicas; j++ { |
|
key := c.hashFnv32([]byte(strconv.Itoa(i) + node.Key + strconv.Itoa(j) + c.salt)) |
|
|
|
if _, ok := c.hashNodes[key]; !ok { |
|
c.hashKeys = append(c.hashKeys, key) |
|
} |
|
c.hashNodes[key] = node |
|
} |
|
} |
|
} |
|
sort.Slice(c.hashKeys, func(i, j int) bool { |
|
return c.hashKeys[i] < c.hashKeys[j] |
|
}) |
|
c.length = len(c.hashKeys) |
|
} |
|
|
|
func (c *ConsistentHashPicker) Pick(key string) (node *PickNode, ok bool) { |
|
if c.length == 0 { |
|
return |
|
} |
|
hash := c.hashFnv32([]byte(key + c.salt)) |
|
|
|
idx := sort.Search(c.length, func(i int) bool { // 二分查找最小为true的index |
|
return c.hashKeys[i] >= hash |
|
}) |
|
if idx == c.length { |
|
idx = 0 |
|
} |
|
node, ok = c.hashNodes[c.hashKeys[idx]] |
|
return |
|
} |
|
|
|
// PickOffset |
|
// key: pick key |
|
// offset: 顺时针第n个节点 |
|
// return node: pick node |
|
// return same: node是否是offset=0时的相同节点 |
|
func (c *ConsistentHashPicker) PickOffset(key string, offset int) (node *PickNode, same bool, ok bool) { |
|
if c.length == 0 { |
|
return |
|
} |
|
hash := c.hashFnv32([]byte(key + c.salt)) |
|
idx := sort.Search(c.length, func(i int) bool { |
|
return c.hashKeys[i] >= hash |
|
}) |
|
if idx == c.length { |
|
idx = 0 |
|
} |
|
hit := (idx + offset) % c.length |
|
if hit < 0 { |
|
hit = c.length + hit |
|
} |
|
node, ok = c.hashNodes[c.hashKeys[hit]] |
|
same = c.hashNodes[c.hashKeys[idx]] == node |
|
return |
|
}
|
|
|