一致性 Hash

传统 hash 取模

实现分布式系统的负载均衡, e.g. idx := ihash(kv.Key) % nReduce
node_number = hash(key) % N # 其中 N 为节点数。

问题在于,当系统出现故障或是扩容导致集群节点数量N变化,映射规则的变化将造成分布式缓存系统之前的缓存失效,引发缓存雪崩

一致性 hash 算法

优点

  • 可扩展:新增或删除节点,数据存储改动很小,不会影响系统
  • 负载均衡:hash值平均分配到nodes
  • 当部分虚拟节点含大量数据时,通过虚拟节点分裂,而不是重新hash,从而避免数据在虚拟节点分布不均衡

流程

  • 将对象和虚拟服务器放到hash环
  • 顺时针查找对象hash值距离最近的服务器,并让服务器缓存该对象
  • 节点变化时,只有少部分对象需要重新分配服务器缓存
  • 引入虚拟节点映射实际服务器,解决负载不均衡问题

缓存映射


增加节点


减少节点


引入虚拟节点

一致性hash实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

// 采取依赖注入,可将crc32算法替换为自定义hash算法
type Hash func(data []byte) uint32

type Map struct {
hash Hash // hash算法
replicas int // 虚拟节点倍数
keys []int // hash环
hashMap map[int]string // 映射表,key是虚拟节点的hash值,value是真实节点名称
}

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) Add(keys ...string) {
// 对每一个真实节点创建replicas个虚拟节点,名称:strconv.Itoa(i)+key
for _, key := range keys {
for i := 0; i < m.replicas; i++ {
// 计算虚拟节点hash并添加到环
hash := int(m.hash([]byte(strconv.Itoa(i) + key)))
m.keys = append(m.keys, hash)
// 建立映射关系
m.hashMap[hash] = key
}
}
// 环上hash排序
sort.Ints(m.keys)
}

func (m *Map) Get(key string) string {
if len(key) == 0 {
return ""
}
// 计算key的hash值,并找到虚拟节点映射的hash(因为是环,采用取余算法)
hash := int(m.hash([]byte(key)))
index := sort.Search(len(m.keys), func(i int) bool {
return m.keys[i] >= hash
})
// 映射得真实节点
return m.hashMap[m.keys[index%len(m.keys)]]
}

分布式系统数据分布算法

指标

  1. 均匀性:负载均衡
  2. 稳定性:在集群节点变化的情况下,算法结果依然稳定
  3. 性能可扩展性:集群规模扩大后,算法运行时间不会显著增加
  4. 节点异构:算法需要应对不同节点的性能/容量差异
  5. 隔离故障区域

演变

Hash

  • 无稳定性

一致性Hash

  • 解决了稳定性。但是集群节点数变化时,会负载不均匀

有负载上限的一致性Hash

  • 实现了负载均衡,但是无法处理节点异构

有虚拟节点的一致性Hash

  • 当实际节点变动时,会影响多个虚拟节点重新分配

分片

  • 分片将Hash环切割成均匀分片,分配给不同节点。将分片作为最小的数据迁移单位,这样当有实际节点变动时,可以将分片交给任意节点
  • BigTable就是将数据切割为分片,将分片信息存在Chubby中

Crush算法

  • 分片算法,优化了分片映射的信息量,支持故障区域划分

cover
画师: ASK
id: 81648738