关于一致性 hash网上有很多讲的非常好的教程,这个就不在此赘述了,就简单的说下。
普通的缓存场景如下:
- 如果有 N 太服务器用来做缓存,那么查询的数据
key,则可以根据hash(key)%N来确定缓存的机器,并进行查询。 - 在机器挂了,或者要新增机器的时候,比较难以处理了。
在普通缓存场景比较棘手的时候,就出现了一致性 hash。该算法将所有的哈希值构成了一个环,其范围在 0 ~ 2^32-1。在查询 key 出现的时候,根据 key 算得 hash 在顺时针找到的第一个服务器上进行查询即可。
但是呢,服务器的分布可能是不均匀的,比如会出现如下这种情况,这个时候大多数 查询的 key 会在节点N1上进行查询。
所以就有了虚拟节点的概念,比如说虚拟节点的个数为2,那么每个节点会计算两次 hash,两次 hash 虽然占用了两个位置,但是都是指向的一个节点。
groupcache中的具体实现
源码中只定义了一个结构体Map如下:
type Hash func(data []byte) uint32
type Map struct {
// hash 函数
hash Hash
// 每个节点的副本数(每个节点在环上实际的节点个数是 replicas,而不是 replicas+1)
replicas int
// 所有节点的 hash 切片,实际中 keys 的长度并不是 2^32-1
keys []int // Sorted
// 上面的 keys 切片中,每个 hash 实际对应的节点
hashMap map[int]string
}
第一个方法就是创建一个Map:
func New(replicas int, fn Hash) *Map {
m := &Map{
replicas: replicas,
hash: fn,
hashMap: make(map[int]string),
}
// 如果函数的 fn 函数是 nil,则传入一个默认的函数
if m.hash == nil {
m.hash = crc32.ChecksumIEEE
}
return m
}
第二个方法是添加节点Add:
func (m *Map) Add(keys ...string) {
for _, key := range keys {
// 每个 key 添加的 replicas 个副本
for i := 0; i < m.replicas; i++ {
// 通过给每个 key 加一个前缀,来产生 hash 值
hash := int(m.hash([]byte(strconv.Itoa(i) + key)))
// 所有的 hash 值都会存放到 keys 切片里面去
m.keys = append(m.keys, hash)
// 虽然添加了多个,但是实际指向的是 key
m.hashMap[hash] = key
}
}
sort.Ints(m.keys)
}
第三个方式是获取查询的节点Get(忽略了IsEmpty方法):
func (m *Map) IsEmpty() bool {
return len(m.keys) == 0
}
func (m *Map) Get(key string) string {
if m.IsEmpty() {
return ""
}
hash := int(m.hash([]byte(key)))
// 通过二分搜索,查找 hash 在数组中应该插入的位置,就是应该查询的节点
idx := sort.Search(len(m.keys), func(i int) bool { return m.keys[i] >= hash })
// 如果索引的值等于 keys 的长度,说明到达环的边界,取值为0
if idx == len(m.keys) {
idx = 0
}
return m.hashMap[m.keys[idx]]
}
一致性 hash demo
package main
func main() {
hash1 := consistenthash.New(2, nil)
hash1.Add("www.baidu.com", "www.google.com")
for i := 0; i < 10; i++ {
fmt.Println(hash1.Get(strconv.Itoa(i)))
}
// Output:
//www.baidu.com
//www.google.com
//www.baidu.com
//www.google.com
//www.baidu.com
//www.google.com
//www.baidu.com
//www.google.com
//www.baidu.com
//www.google.com
}
这个 demo 中节点的副本数都是2,在查询0到9的值得时候,分布的是比较均匀的,百度和谷歌依次出现。