索引
【go项目-geecache】动手写分布式缓存 - day1 - 实现LRU算法
【go项目-geecache】动手写分布式缓存 - day2 - 单机并发缓存
【go项目-geecache】动手写分布式缓存 - day3 - HTTP 服务端
【go项目-geecache】动手写分布式缓存 - day4 - 一致性哈希(hash)
【go项目-geecache】动手写分布式缓存 - day5 - 分布式节点
【go项目-geecache】动手写分布式缓存 - day6 - 防止缓存击穿
【go项目-geecache】动手写分布式缓存 - day7 - 使用 Protobuf 通信
收获
- 学会了什么是一致性哈希 ; 解决分布式负载问题
- 学会如何实现一致性哈希
一致性哈希是什么?
一致性哈希(Consistent Hashing)是一种常用的哈希算法,主要用于解决分布式系统中的负载均衡问题。它的主要思想是将数据和节点映射到一个虚拟环形空间中,通过一致的哈希函数将数据均匀地分布在环上,然后根据节点在环上的位置将数据映射到相应的节点上,从而实现负载均衡。
一致性哈希具体实现思想 :
-首先把整个哈希值空间变成一个环,然后把key哈希放在环上,接着把value哈希放在环上,我们让value哈希的位置的顺时针的下一个key哈希作为对应关系 ,因为整个哈希空间是个环,这样即使某些节点增加或者减少,也只需要修改节点附近的对应关系
数据分布不均 :采用虚拟节点
可能有些节点会对应大多数的数据,这样分布不均,我们添加为每个节点添加几个虚拟节点,这样每个虚拟节点分布比较均匀,最后在把虚拟节点映射回真实节点
分布式哈希实现
结构体实现
package consistenthash
import (
"hash/crc32"
"sort"
"strconv"
)
type Hash func(data []byte) uint32
type Map struct {
hash Hash
replicas int
keys []int // Sorted
hashMap map[int]string
}
hash
:表示哈希函数,为一个Hash
类型的函数变量。replicas
:表示每个键值对在哈希表中的副本数量。keys
:表示哈希表中所有键的有序数组。hashMap
:表示哈希表,为一个从int
类型的键到string
类型的值的映射。
实现实例化函数
func New(replicas int, fn Hash) *Map {
m := &Map{
replicas: replicas,
hash: fn,
hashMap: make(map[int]string),
}
if m.hash == nil {
m.hash = crc32.ChecksumIEEE
}
return m
}
这里如果hash函数为空,那么默认调用32位的CRC校验值
实现add函数
func (m *Map) Add(keys ...string) {
for _, key := range keys {
for i := 0; i < m.replicas; i++ {
hash := int(m.hash([]byte(strconv.Itoa(i) + key)))
m.keys = append(m.keys, hash)
m.hashMap[hash] = key
}
}
sort.Ints(m.keys)
}
add()
: 用于向哈希表中添加一个或多个键值对。该方法接受一个或多个string
类型的键作为参数,并将这些键映射到哈希表中的位置。
实现Get函数
func (m *Map) Get(key string) string {
if len(m.keys) == 0 {
return ""
}
hash := int(m.hash([]byte(key)))
// Binary search for appropriate replica.
idx := sort.Search(len(m.keys), func(i int) bool {
return m.keys[i] >= hash
})
return m.hashMap[m.keys[idx%len(m.keys)]]
}
Get()
: 传入字符串,得到哈希值字符串- 首先判断是否存在key
- 用哈希函数计算哈希值
- 二分查找找到第一个匹配的虚拟节点的下标
idx
,从 m.keys 中获取到对应的哈希值 - 通过hashmap映射得到真实节点
实现代码和测试代码
consistenthash.go
package consistenthash
import (
"hash/crc32"
"sort"
"strconv"
)
type Hash func(data []byte) uint32
type Map struct {
hash Hash
replicas int
keys []int // Sorted
hashMap map[int]string
}
func New(replicas int, fn Hash) *Map {
m := &Map{
replicas: replicas,
hash: fn,
hashMap: make(map[int]string),
}
if m.hash == nil {
m.hash = crc32.ChecksumIEEE
}
return m
}
func (m *Map) Get(key string) string {
if len(m.keys) == 0 {
return ""
}
hash := int(m.hash([]byte(key)))
// Binary search for appropriate replica.
idx := sort.Search(len(m.keys), func(i int) bool {
return m.keys[i] >= hash
})
return m.hashMap[m.keys[idx%len(m.keys)]]
}
func (m *Map) Add(keys ...string) {
for _, key := range keys {
for i := 0; i < m.replicas; i++ {
hash := int(m.hash([]byte(strconv.Itoa(i) + key)))
m.keys = append(m.keys, hash)
m.hashMap[hash] = key
}
}
sort.Ints(m.keys)
}
test_consistenthash
package consistenthash
import (
"strconv"
"testing"
)
func TestHashing(t *testing.T) {
hash := New(3, func(key []byte) uint32 {
i, _ := strconv.Atoi(string(key))
return uint32(i)
})
// Given the above hash function, this will give replicas with "hashes":
// 2, 4, 6, 12, 14, 16, 22, 24, 26
hash.Add("6", "4", "2")
testCases := map[string]string{
"2": "2",
"11": "2",
"23": "4",
"27": "2",
}
for k, v := range testCases {
if hash.Get(k) != v {
t.Errorf("Asking for %s, should have yielded %s", k, v)
}
}
// Adds 8, 18, 28
hash.Add("8")
// 27 should now map to 8.
testCases["27"] = "8"
for k, v := range testCases {
if hash.Get(k) != v {
t.Errorf("Asking for %s, should have yielded %s", k, v)
}
}
}