-
缓存雪崩
:缓存在同一时刻全部失效(例如设置相同过期时间,服务器宕机),均需要去访问DB,造成DB瞬间访问量过大- 解决1:设置不同的过期时间
- 解决2:保证热点数据永不过期
- 解决3:缓存降级,降级是指返回一些默认值或者部分数据
- 解决4:熔断,请求量过高时,自动拒绝请求
-
一致性哈希
- 过程
- 1.开辟一个大小为 2 32 2^{32} 232的空间,将数字首尾相连形成环
- 2.计算节点的
Hash
值,并映射到环的对应位置 - 3.计算
key
的Hash
值,找到距离key最近的节点,发送请求
- 数据倾斜
- 节点数量过小,会导致key都被分配给某个节点
- 解决:为真实节点引入虚拟节点,同时维护真实节点和虚拟节点的映射关系
- 过程
-
day4
需要实现一致性哈希的过程,一致性哈希的主要结构由Map
进行维护Map
中有四个熟悉:hash
、replicas
、keys
、hashMap
hash
:哈希函数类型,这样定义允许在一致性哈希中使用不同的哈希函数replicas
:表示一个真实节点对应的虚拟节点的数量keys
:整形切片,存储某个真实节点对应的虚拟节点的哈希值,方便二分快速定位虚拟节点的位置(实际是找大于等于key哈希值的最小节点)hashMap
:虚拟节点->真实节点的映射
-
一致性哈希代码
-
package consistenthash import ( "hash/crc32" "sort" "strconv" ) type hash func(data []byte) uint32 type Map struct { hash hash // 哈希函数 replicas int // 对应虚拟节点数量 keys []int // 虚拟节点列表 hashMap map[int]string // 虚拟节点->真实节点映射 } func New(replicas int, fn hash) *Map { m := &Map{ hash: fn, replicas: replicas, hashMap: make(map[int]string), } if m.hash == nil { m.hash = crc32.ChecksumIEEE } return m } func (m *Map) Add(keys ...string) { for _, key := range keys { for i := 0; i < m.replicas; i++ { hash := m.hash([]byte(strconv.Itoa(i) + key)) // 通过在前缀添加数字作为虚拟节点 m.keys = append(m.keys, int(hash)) m.hashMap[int(hash)] = key } } sort.Ints(m.keys) // 排序方便二分 } func (m *Map) Get(key string) string { if len(m.keys) == 0 { return "" } hash := m.hash([]byte(key)) idx := sort.Search(len(m.keys), func(i int) bool { // 二分查找最近的节点 return m.keys[i] >= int(hash) }) return m.hashMap[m.keys[idx%len(m.keys)]] }
-
-
测试代码
-
package consistenthash import ( "strconv" "testing" ) func TestHashing(t *testing.T) { hash := New(3, func(key []byte) uint32 { // 假设key全为数字字符串,因此自定义hash值为对应数字 i, _ := strconv.Atoi(string(key)) return uint32(i) }) hash.Add("6", "4", "2") testDates := map[string]string{ // key应该对应的节点 "2": "2", "11": "2", "23": "4", "27": "2", } for k, v := range testDates { if hash.Get(k) != v { t.Errorf("%s获取节点错误~", k) } } hash.Add("8") testDates["27"] = "8" for k, v := range testDates { if hash.Get(k) != v { t.Errorf("%s获取节点错误~", k) } } }
-