一致性 Hash(Consistent Hashing)详解
一致性 Hash 是分布式缓存系统中解决「数据如何均匀分布 + 节点变化时如何减少数据迁移」问题的一种算法。
它最经典的应用场景:
-
Redis 集群数据分片
-
Memcached 分布式缓存
-
CDN 节点调度
-
分布式数据库分片
-
服务节点负载均衡
1. 为什么需要一致性 Hash?
先看传统 Hash 分片。
假设我们有 3 台缓存服务器:
Cache-A
Cache-B
Cache-C
现在有数据:
user:1001
user:1002
user:1003
...
我们希望把数据均匀放到不同机器。
最简单方法:
服务器编号 = hash(key) % 节点数量
例如:
node = hash("user:1001") % 3
结果:
hash(user:1001)=100
100 % 3 = 1
放到 Cache-B
结构:
hash(key)%3
user:1001 ----------> Cache-B
user:1002 ----------> Cache-A
user:1003 ----------> Cache-C
看起来很好。
2. 问题在哪里?
假设业务增长,需要增加一台机器:
之前:
3个节点
Cache-A
Cache-B
Cache-C
变成:
4个节点
Cache-A
Cache-B
Cache-C
Cache-D
计算方式变了:
以前:
hash(key)%3
现在:
hash(key)%4
大量数据映射都会变化。
例如:
| key | 原节点 | 新节点 |
|---|---|---|
| user:1 | A | C |
| user:2 | B | D |
| user:3 | C | A |
| user:4 | A | B |
结果:
原来的缓存:
100万个key
A ---------------- 33万
B ---------------- 33万
C ---------------- 34万
增加D之后:
A ---------------- 25万
B ---------------- 25万
C ---------------- 25万
D ---------------- 25万
大量 key 迁移。
造成:
1. 缓存雪崩
大量请求同时访问数据库:
缓存:
user:1 miss
user:2 miss
user:3 miss
...
↓
数据库压力暴增
2. 网络迁移压力
需要搬:
Cache-A
|
| 大量数据复制
↓
Cache-D
所以:
普通 Hash 最大的问题:节点数量变化导致几乎所有 key 重新分布。
3. 一致性 Hash 的核心思想
一致性 Hash 不再:
hash(key) % 节点数量
而是:
把整个 Hash 空间组织成一个环。
例如:
假设 Hash 值范围:
0 ~ 999
首尾连接:
0
/ \
900 100
800 200
700 300
\ /
500
这叫:
Hash Ring(哈希环)
4. 节点如何放入 Hash 环?
服务器也进行 Hash:
例如:
hash(Cache-A)=100
hash(Cache-B)=400
hash(Cache-C)=700
放入环:
0
Cache-A
|
100 ----------------
400
|
Cache-B
700
|
Cache-C
5. 数据如何寻找节点?
数据 key 也 Hash:
例如:
hash(user:1001)=250
放到环:
0
Cache-A
|
100
250 <---- user:1001
400
|
Cache-B
700
|
Cache-C
规则:
顺时针找到第一个节点。
所以:
user:1001
250
顺时针
↓
Cache-B
存储:
user:1001
↓
Cache-B
6. 一致性 Hash 最大优势
增加节点
现在增加:
Cache-D
计算:
hash(Cache-D)=550
加入:
100 Cache-A
400 Cache-B
550 Cache-D
700 Cache-C
以前:
400 ~ 700
全部属于 Cache-C
现在:
400 ~ 550
属于 Cache-D
只有这一部分数据变化:
原 Cache-C:
400~700
变成:
400~550 Cache-D
550~700 Cache-C
也就是说:
新增一个节点:
只影响环上的一小部分数据。
7. 删除节点怎么办?
假设:
Cache-B
挂掉。
原来:
Cache-A
↓
Cache-B
↓
Cache-C
Cache-B 负责:
100 ~ 400
删除后:
Cache-A
Cache-C
那么:
100~400
顺时针找到 Cache-C
所以:
Cache-B 数据自动迁移到 Cache-C。
8. 但是还有一个问题:数据倾斜
普通一致性 Hash:
Cache-A
Cache-B
Cache-C
可能:
Cache-A负责 70%
Cache-B负责20%
Cache-C负责10%
原因:
节点随机落在环上。
9. 虚拟节点(Virtual Node)
解决方式:
让一个真实节点拥有多个 Hash 位置。
例如:
以前:
Cache-A
hash(Cache-A)=100
变成:
Cache-A-1
hash(Cache-A#1)=100
Cache-A-2
hash(Cache-A#2)=300
Cache-A-3
hash(Cache-A#3)=800
环:
100 A
200 B
300 A
400 C
600 B
800 A
效果:
节点分布更加均匀。
实际系统:
例如:
真实节点:
Redis-1
虚拟节点:
Redis-1#001
Redis-1#002
...
Redis-1#200
10. 一致性 Hash 数据结构
通常实现:
HashRing
TreeMap
key(hash值)
|
|
↓
100 -> Node-A
250 -> Node-B
400 -> Node-C
700 -> Node-D
查找:
hash(key)
|
↓
TreeMap.ceilingEntry(hash)
找到第一个 >= hash 的节点
例如:
key hash = 350
TreeMap:
100 A
250 B
400 C
700 D
ceilingEntry(350)
返回:
400 C
11. Redis Cluster 和一致性 Hash 的区别
很多人会混淆。
Memcached
经典:
一致性Hash
key
↓
hash ring
↓
server
Redis Cluster
不是一致性 Hash。
Redis 使用:
16384 个 slot
结构:
key
↓
CRC16(key)
↓
slot
↓
Redis节点
例如:
slot 1000
↓
Redis-A
slot 8000
↓
Redis-B
优势:
-
节点迁移更可控
-
运维方便
12. 一致性 Hash 解决什么问题?
总结:
| 问题 | 普通Hash | 一致性Hash |
|---|---|---|
| 节点增加 | 大量迁移 | 少量迁移 |
| 节点删除 | 大量失效 | 局部影响 |
| 负载均衡 | 较好 | 需要虚拟节点 |
| 实现复杂度 | 简单 | 复杂 |
| 适合动态集群 | ❌ | ✅ |
13. 一句话理解
一致性 Hash 就是在所有服务器和数据之间建立一个「哈希环」,数据顺时针找到最近的服务器;当服务器增加或减少时,只影响环上一小部分数据,从而避免大量缓存失效。
一个非常直观的类比
普通 Hash:
像:
100个人按照身份证号码 % 3 分到3个房间。
增加一个房间:
身份证 % 4
所有人重新分房。
一致性 Hash:
像:
让房间固定坐在一个圆桌旁,人根据自己的编号顺时针找最近房间。
增加一个房间:
只需要接管附近的一部分人。
这就是为什么分布式缓存(尤其 Memcached)大量使用一致性 Hash。