再谈一致性哈希
最近在好未来的面试中, 面试官问到了一致性哈希的问题, 但是自己当时脑子直接抽掉了, 根本没有回答出来, 也可能导致自己直接失去工作机会, 所以在此重新整理下一致性哈希的问题, 希望之后再遇到类似的场景能够尽可能的回答出来.
适用场景
一致性哈希算法就很好地解决了分布式系统在扩容或者缩容时,发生过多的数据迁移的问题。
一致哈希算法也用了取模运算,但与哈希算法不同的是,哈希算法是对节点的数量进行取模运算,而一致哈希算法是对 2^32 进行取模运算,是一个固定的值。
我们可以把一致哈希算法是对 2^32 进行取模运算的结果值组织成一个圆环,就像钟表一样,钟表的圆可以理解成由 60 个点组成的圆,而此处我们把这个圆想象成由 2^32 个点组成的圆,这个圆环被称为哈希环,如下图:
技术要点
- 原始一致性哈希

形成哈希环, 从该点向后查找, 查找到的第一个节点即为应该分配到的节点 - 增加虚拟节点的一致性哈希

优点:
节点数量多了后,节点在哈希环上的分布就相对均匀了。
当节点变化时,会有不同的节点共同分担系统的变化,因此稳定性更高。
示例代码
package conshash
import (
"sort"
"strconv"
)/
const BasicVirtualSlots = 10
type HashFunc func(s string) int
type HashRing struct {
hash HashFunc
slotsMap map[int]string
slotsList []int
}
type ConsHash interface {
AddNode(s string)
GetNode(s string)
RemoveNode(s string)
}
func NewHashRing(hashFunc HashFunc) *HashRing {
return &HashRing{
hash: hashFunc,
slotsMap: make(map[int]string),
slotsList: make([]int, 0),
}
}
func (h *HashRing) AddNode(s string) {
for i := 0; i < BasicVirtualSlots; i++ {
hashK := h.hash(s + "*9182374$" + strconv.Itoa(i))
h.slotsList = append(h.slotsList, hashK)
h.slotsMap[hashK] = s
}
sort.Ints(h.slotsList)
}
func (h *HashRing) GetNode(s string) string {
if len(h.slotsList) == 0 {
return ""
}
hashK := h.hash(s)
idx := sort.Search(
len(h.slotsList), func(i int) bool {
// print("get: i -> ")
// println(i)
return h.slotsList[i] >= hashK
},
)
idx = idx % len(h.slotsList)
return h.slotsMap[h.slotsList[idx]]
}
func (h *HashRing) RemoveNode(s string) {
for i := 0; i < BasicVirtualSlots; i++ {
hashK := h.hash(s + ";" + strconv.Itoa(i))
delete(h.slotsMap, hashK)
}
}