一致性哈希的理解与实践

一致性哈希的理解与实践

维基百科中这样定义一致性哈希算法:

一致哈希 是一种特殊的哈希算法。在使用一致哈希算法后,哈希表槽位数(大小)的改变平均只需要对K/n 个关键字重新映射,其中K是关键字的数量,n是槽位数量。然而在传统的哈希表中,添加或删除一个槽位几乎需要对所有关键字进行重新映射。

好吧,初次接触,看不懂,我们从传统的哈希算法开始讲起。

传统哈希算法

随着业务的发展,数据量的提升,我们需要增加服务器节点。对于客户端发来的请求,我们首先通过一个Proxy层,由Proxy层处理来自客户端的读写请求,接收到读写请求后,通过对 Key 做哈希找到对应的节点。如下图所示:

通过哈希算法,每个 key 都可以寻址到对应的服务器,假设客户端发过来的请求是 key-01,计算公式为 hash(key-01) % 3 ,经过计算寻址到了编号为 1 的服务器节点 A,如下图所示。

但如果服务器数量发生变化,基于新的服务器数量来执行哈希算法的时候,就会出现路由寻址失败的情况,Proxy 无法找到之前寻址到的那个服务器节点,这是为什么呢?想象一下,假如 3 个节点不能满足业务需要了,这时我们增加了一个节点,节点的数量从 3 变化为 4,那么之前的 hash(key-01) % 3 = 1,就变成了 hash(key-01) % 4 = X,因为取模运算发生了变化,所以这个 X 大概率不是 1,这时你再查询,就会找不到数据了,因为 key-01 对应的数据,存储在节点 A 上,而不是节点 B。同样的道理,如果我们需要下线 1 个服务器节点(也就是缩容),也会存在类似的可能查询不到数据的问题。

为了解决这一问题,我们就需要迁移数据,基于新的计算公式 hash(key-01) % 4 ,来重新对数据和节点做映射。需要注意的是,数据的迁移成本是非常高的。通过一个具体的场景进行说明:

假定有1000万条数据,原先存储在3个节点上,如果我们增加1个节点,需要迁移多少数据?

下面通过代码验证:(完整代码在这里

// 传统的哈希映射
func hash(key int, nodes int) int {
	return key % nodes
}
// migrate:需要迁移的数据量
// keys:数据量(1000万)
// nodes:旧集群的节点个数
// newNodes:新集群的节点个数
// migrateRatio:数据迁移率
migrate := 0
for i := 0; i < keys; i++ {
	if hash(i, nodes) != hash(i, newNodes) {
		migrate++
	}
}
migrateRatio := float64(migrate) / float64(keys)

通过执行:

$ go run ./hash.go  -keys 10000000 -nodes 3 -new-nodes 4
74.999980%

可以看到,我们需要迁移75%的数据。因为普通的哈希映射强依赖于集群的节点个数,当增加或减少节点个数时,映射关系就会发生动荡的变化,这在生产环境中是不可容忍的。

为了解决这一问题,就引出了一致性哈希算法

一致哈希算法

原理:哈希环

一致哈希算法也用了取模运算,但与哈希算法对节点的数量进行取模运算不同,一致哈希算法是对 2^32 进行取模运算。你可以想象下,一致哈希算法,将整个哈希值空间组织成一个虚拟的圆环,也就是哈希环。

在一致哈希中,通过执行哈希算法(为了演示方便,假设哈希算法函数为“c-hash()”),将节点映射到哈希环上 (通常选择节点的主机名、ip地址等作为参数执行 c-hash()),如下图所示:

当需要对指定 key 的值进行读写的时候,你可以通过下面 2 步进行寻址:

  1. 首先,将 key 作为参数执行 c-hash() 计算哈希值,并确定此 key 在环上的位置;
  2. 然后,从这个位置沿着哈希环顺时针“行走”,遇到的第一节点就是 key 对应的节点。

这一过程如下图所示:

可以看到,在上图中,key-01 映射到了节点A,key-02 映射到了节点B,key-03 映射到了节点C。如果节点C宕机了,那么根据寻址规则,只需要把key-03重新定位到节点A即可,而key-01,key-02不需要改变。也就是说,一致性哈希算法,在新增/减少节点时,只需要重新定位该节点附近的一小部分数据,而不需要重新定位所有的节点。这样就有效的解决了“增加/减少节点时需要大规模迁移数据”的问题。

问题:数据倾斜

当服务器节点较少时,容易造成节点位置分布不均的情况,从而造成数据倾斜。如下图所示,由于节点分布不均,使得大量的访问请求集中到节点A上,造成缓存节点负载不均衡。这就是数据倾斜问题。

为了解决数据倾斜问题,引入了虚拟节点的概念。

改进:引入虚拟节点

所谓虚拟节点,就是对每一个服务器节点计算多个哈希值,在每个计算结果位置上,都放置一个虚拟节点,并将虚拟节点映射到实际节点。假设1个真实节点对应2个虚拟节点,那么节点A对应的虚拟节点是node-A-01,node-A-02(通常以添加编号的方式实现),其余节点类似。于是就形成了6个虚拟节点,如下图所示:

随由于节点数增多了,分布自然会变得均匀了。当寻址时,计算key的哈希值,在环上顺时针寻找应该选取的虚拟节点,比如key-01选择虚拟节点node-B-02,那么就把该请求映射到真实节点B上。

虚拟节点扩充了节点的数量,解决了节点较少的情况下数据容易倾斜的问题,而且代价非常小,只需要增加一个字典(map)维护真实节点与虚拟节点的映射关系即可。

实现:


参考:

  1. 极客时间专栏:https://time.geekbang.org/column/article/207426

猜你喜欢

转载自www.cnblogs.com/kkbill/p/12728325.html