分布式系统中对象与节点的映射关系,传统方案是使用对象的哈希值,对节点个数取模,再映射到相应编号的节点,这种方案在节点个数变动时,绝大多数对象的映射关系会失效而需要迁移;而一致性哈希算法中,当节点个数变动时,映射关系失效的对象非常少,迁移成本也非常小。本文总结了一致性哈希的算法原理和Java实现,并列举了其应用。
作者:王克锋
出处:https://kefeng.wang/2018/08/10/consistent-hashing/
版权:自由转载-非商用-非衍生-保持署名,转载请标明作者和出处。
分布式系统中,假设有 n 个节点,传统方案使用 mod(key, n)
映射数据和节点。
当扩容或缩容时(哪怕只是增减1个节点),映射关系变为 mod(key, n+1)
/ mod(key, n-1)
,绝大多数数据的映射关系都会失效。
1997年,麻省理工学院(MIT)的 David Karger 等6个人发布学术论文《Consistent hashing and random trees: distributed caching protocols for relieving hot spots on the World Wide Web(一致性哈希和随机树:用于缓解万维网上热点的分布式缓存协议)》,对于 K 个关键字和 n 个槽位(分布式系统中的节点)的哈希表,增减槽位后,平均只需对 K/n 个关键字重新映射。
评估一个哈希算法的优劣,有如下指标,而一致性哈希全部满足:
原始论文《Consistent Hashing and Random Trees》链接如下:
相关论文《Web Caching with Consistent Hashing》链接如下:
更多资料:
WikiPedia - Consistent hashing
codeproject - Consistent hashing
2.1.1 公用哈希函数和哈希环
设计哈希函数 Hash(key),要求取值范围为 [0, 2^32)
各哈希值在上图 Hash 环上的分布:时钟12点位置为0,按顺时针方向递增,临近12点的左侧位置为2^32-1。
2.1.2 节点(Node)映射至哈希环
如图哈希环上的绿球所示,四个节点 Node A/B/C/D,
其 IP 地址或机器名,经过同一个 Hash() 计算的结果,映射到哈希环上。
2.1.3 对象(Object)映射于哈希环
如图哈希环上的黄球所示,四个对象 Object A/B/C/D,
其键值,经过同一个 Hash() 计算的结果,映射到哈希环上。
2.1.4 对象(Object)映射至节点(Node)
在对象和节点都映射至同一个哈希环之后,要确定某个对象映射至哪个节点,
只需从该对象开始,沿着哈希环顺时针方向查找,找到的第一个节点,即是。
可见,Object A/B/C/D 分别映射至 Node A/B/C/D。
现实场景:服务器缩容时删除节点,或者有节点宕机。如下图,要删除节点 Node C:
只会影响欲删除节点(Node C)与上一个(顺时针为前进方向)节点(Node B)与之间的对象,也就是 Object C,
这些对象的映射关系,按照 2.1.4 的规则,调整映射至欲删除节点的下一个节点 Node D。
其他对象的映射关系,都无需调整。
现实场景:服务器扩容时增加节点。比如要在 Node B/C 之间增加节点 Node X:
只会影响欲新增节点(Node X)与上一个(顺时针为前进方向)节点(Node B)与之间的对象,也就是 Object C,
这些对象的映射关系,按照 2.1.4 的规则,调整映射至新增的节点 Node X。
其他对象的映射关系,都无需调整。
对于前面的方案,节点数越少,越容易出现节点在哈希环上的分布不均匀,导致各节点映射的对象数量严重不均衡(数据倾斜);相反,节点数越多越密集,数据在哈希环上的分布就越均匀。
但实际部署的物理节点有限,我们可以用有限的物理节点,虚拟出足够多的虚拟节点(Virtual Node),最终达到数据在哈希环上均匀分布的效果:
如下图,实际只部署了2个节点 Node A/B,
每个节点都复制成3倍,结果看上去是部署了6个节点。
可以想象,当复制倍数为 2^32 时,就达到绝对的均匀,通常可取复制倍数为32或更高。
虚拟节点哈希值的计算方法调整为:对“节点的IP(或机器名)+虚拟节点的序号(1~N)”作哈希。
一致性哈希算法有多种具体的实现,包括 Chord 算法,KAD 算法等,都比较复杂。
这里给出一个简易实现及其演示,可以看到一致性哈希的均衡性和单调性的优势。
单调性在本例中没有统计数据,但根据前面原理可知,增删节点后只有很少量的数据需要调整映射关系。
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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
/** * @author: https://kefeng.wang * @date: 2018-08-10 11:08 **/ public class ConsistentHashing { // 物理节点 private Set |
修改代码中 VIRTUAL_COPIES = 1
(相当于没有虚拟节点),运行结果如下(可见各节点负荷很不均衡):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
======== 初始情况 ======== IP=192.168.1.101: RATE=45% IP=192.168.1.102: RATE=3% IP=192.168.1.103: RATE=28% IP=192.168.1.104: RATE=22% ======== 删除物理节点 ======== IP=192.168.1.101: RATE=45% IP=192.168.1.102: RATE=3% IP=192.168.1.104: RATE=51% ======== 添加物理节点 ======== IP=192.168.1.101: RATE=45% IP=192.168.1.102: RATE=3% IP=192.168.1.104: RATE=32% IP=192.168.1.108: RATE=18% |
修改代码中 VIRTUAL_COPIES = 32
,运行结果如下(可见各节点负荷比较均衡):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
======== 初始情况 ======== IP=192.168.1.101: RATE=29% IP=192.168.1.102: RATE=21% IP=192.168.1.103: RATE=25% IP=192.168.1.104: RATE=23% ======== 删除物理节点 ======== IP=192.168.1.101: RATE=39% IP=192.168.1.102: RATE=37% IP=192.168.1.104: RATE=23% ======== 添加物理节点 ======== IP=192.168.1.101: RATE=35% IP=192.168.1.102: RATE=20% IP=192.168.1.104: RATE=23% IP=192.168.1.108: RATE=20% |
修改代码中 VIRTUAL_COPIES = 1048576
,运行结果如下(可见各节点负荷非常均衡):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
======== 初始情况 ======== IP=192.168.1.101: RATE=24% IP=192.168.1.102: RATE=24% IP=192.168.1.103: RATE=25% IP=192.168.1.104: RATE=25% ======== 删除物理节点 ======== IP=192.168.1.101: RATE=33% IP=192.168.1.102: RATE=33% IP=192.168.1.104: RATE=33% ======== 添加物理节点 ======== IP=192.168.1.101: RATE=25% IP=192.168.1.102: RATE=24% IP=192.168.1.104: RATE=24% IP=192.168.1.108: RATE=24% |
一致性哈希是分布式系统组件负载均衡的首选算法,它既可以在客户端实现,也可以在中间件上实现。其应用有: