分布式存储之一致性Hash算法

142 阅读6分钟

一致性Hash算法是一个为了解决分布式系统中经典难题而生的、非常精巧且实用的算法。

一、核心问题:为什么要有一致性Hash?

我们先看一个没有一致性Hash时的问题,这能让你明白它存在的价值。

假设我们有一个巨大的缓存系统,数据太多,一台Redis服务器存不下。我们决定用3台Redis服务器(节点)来分担存储压力。那么,如何决定一条数据(例如,键为 user:1001)该存到哪台服务器上呢?

最直观的方法:Hash取模

  • 公式:服务器下标 = hash(Key) % 服务器数量
  • 例子:hash("user:1001") % 3 = 2,那么这条数据就存放在第3台服务器上。

这看起来很好,但存在一个致命缺陷:扩容或缩容时,缓存会“雪崩”

假设现在3台服务器已经存了1亿条数据。这时需要增加一台服务器变成4台。

  • 取模公式变成了:hash(Key) % 4
  • 这意味着绝大多数数据计算出的新下标都和原来不一样了。例如,原来 hash("user:1001") % 3 = 2,现在 hash("user:1001") % 4 很可能不等于2。
  • 后果:当应用来请求 user:1001 时,请求会被路由到新的服务器(比如第4台),但那里根本没有这条数据!缓存全部失效,所有请求会瞬间穿透到后端数据库,数据库毫无防备,很可能直接被巨大的流量冲垮。

传统Hash取模就像固定数量的快递柜, 你有3个快递柜(节点),根据包裹单号(Key)除以3的余数决定放哪个柜子。现在增加第4个柜子,规则变成了除以4的余数。这意味着几乎所有包裹都要被重新取出,然后按照新规则放入新的柜子里。这是一个工作量巨大且服务会中断的过程。

一致性Hash算法的目标就是在增加或减少服务器时,尽可能地减少需要重新映射的数据量,避免缓存雪崩。

二、一致性Hash算法原理--环状结构

一致性Hash算法提出了一个非常聪明的构想:不再对服务器的数量取模,而是构建一个巨大的虚拟环

  1. 构建哈希环。想象一个由 0 到 2^32 - 1 组成的圆环。首尾相连。

  2. 将节点(服务器)放到环上。对每个服务器节点(如它的IP或主机名)计算哈希值,这个哈希值会落在环的某个位置上。假设我们有3台服务器:Node-A, Node-B, Node-C。通过计算,它们被放置到环上。

  3. 将数据(Key)放到环上。对每个要存储的数据的Key(如 user:1001)计算哈希值,也将其映射到环上。

  4. 为数据寻找节点。从数据的Key在环上的位置出发,顺时针方向寻找,遇到的第一台服务器,就是它应该存放的服务器。如下图,Key-1 找到 Node-A,Key-2 找到 Node-B,Key-3 找到 Node-C。

image.png

三、一致性Hash算法如何解决扩容问题?

现在,我们来看看最精彩的部分:新增一台服务器 Node-D 会发生什么。

  1. Node-D 根据其IP计算哈希值,并被插入到哈希环的某个位置(假设在Node-B和Node-C之间)。
  2. 受影响的只有原本顺时针属于Node-C,但现在更靠近Node-D的这一部分数据。
  3. 我们只需要将这一部分数据从Node-C迁移到新的Node-D上即可。
  4. 其他所有的数据,例如原本在Node-A和Node-B上的,完全不受影响。

image.png

对比之前的Hash取模法(几乎全部数据要重新迁移),一致性Hash算法只影响了环上的一小部分数据,完美地避免了缓存雪崩。

一致性Hash就像圆桌吃饭,服务器是坐在圆桌旁的人,数据是端上来的菜。上菜规则是:每道菜顺时针往下传,遇到谁就放在谁面前。

  1. 原来3个人(A,B,C)吃饭。
  2. 现在新来一个人D,坐在了B和C之间。
  3. 受影响的只有:原本从B这边传过来、本该传给C的菜,现在会先传到D面前。
  4. 不受影响的是:A和B之间的菜,依然传给B;C和A之间的菜,依然要绕过桌子传给A。
  5. 只需要把D和C之间那几盘菜重新分配一下即可,绝大部分菜都不用动。

四、虚拟节点:解决数据倾斜问题

基础的一致性Hash算法还有一个潜在问题:数据倾斜。如果服务器节点在环上分布不均匀,就会导致大量数据集中在某个节点上,而其他节点很空闲。这就失去了负载均衡的意义。

解决方案就是:虚拟节点。不再将一台服务器映射到环上的一个点,而是映射到多个虚拟节点上。例如,对Node-A,不是计算 hash("Node-A"),而是计算 hash("Node-A#1")hash("Node-A#2")hash("Node-A#3") ... hash("Node-A#1000"),将这1000个虚拟节点放到环上。对Node-B、Node-C也做同样的事。由于哈希函数的特性,这些虚拟节点会均匀地分散在环上。这样,即使真实服务器很少,它们在环上也拥有了大量的、分布均匀的代表点。数据通过虚拟节点最终会近乎均匀地映射到真实的物理服务器上。

image.png

五、实际应用案例

一致性Hash算法是构建大型分布式系统的基石技术之一,以下是它常见的几个实际应用场景。

  • 分布式缓存:Memcached和Redis集群等片模式的核心算法。扩容缩容时影响最小。
  • 负载均衡:Nginx、Dubbo等负载均衡器可以用它实现会话保持(Sticky Session),将同一客户端的请求总是转发到同一台后端服务器。
  • 分布式数据库/存储系统:如Cassandra、DynamoDB,用它来做数据分片(Sharding),决定数据存储在哪个分片上。
  • CDN网络:用于将用户请求路由到最近的边缘节点。

六、总结

特性传统Hash取模一致性Hash
伸缩性极差:节点数变动时,几乎所有数据需要重映射,导致缓存雪崩极好:节点变动仅影响环上相邻部分数据,缓存命中率高
数据均衡均衡基础版可能倾斜,需引入虚拟节点技术来保证均衡。
复杂度简单相对复杂