一致性 Hash 是什么?为什么它是分布式系统中的核心算法

很多程序员第一次接触“一致性 Hash”,都会有一种感觉:

这个名字听起来很高级。

但真正去看资料时,却发现各种概念扑面而来:

  • Hash Ring(哈希环)
  • 虚拟节点
  • 顺时针查找
  • 数据迁移
  • 分布式缓存

最后越看越晕。

我第一次学习一致性 Hash 的时候,也是这种感觉。

直到后来真正做了分布式系统、Redis 集群、分库分表之后,才真正理解:

一致性 Hash 本质上其实是在解决一个非常现实的问题。

也就是:

当服务器数量变化时,如何尽量减少数据迁移。

今天这篇文章,我会尽量用“小白也能看懂”的方式,把一致性 Hash 讲清楚。

包括:

  • 为什么会有一致性 Hash
  • 普通 Hash 的问题是什么
  • 一致性 Hash 如何解决问题
  • 什么是 Hash 环
  • 什么是虚拟节点
  • Redis 为什么后来不用一致性 Hash
  • 工程里真正的使用场景

在进入主题之前,可以先直观的看下一致性hash的原理视频:

5月18日

更多精彩的算法动画,可以访问:算法动图

一、从一个最简单的问题开始

假设现在有 3 台服务器:

ServerA
ServerB
ServerC

现在我们需要存储用户数据:

user_1
user_2
user_3
...

那么问题来了:

user_1 应该放到哪台服务器?


最简单的方法是什么?

很多人第一反应就是:

hash(userId) % 服务器数量

例如:

hash(user_1) % 3 = 0 -> ServerA
hash(user_2) % 3 = 1 -> ServerB
hash(user_3) % 3 = 2 -> ServerC

这就是最经典的:

普通 Hash 分片

它的优点非常明显:

  • 简单
  • 数据比较均匀

很多系统最开始都是这么做的。


二、普通 Hash 最大的问题

问题出现在:

扩容

假设原来:

3 台服务器

后来业务量越来越大。

你发现:

ServerA 快扛不住了。

于是决定:

新增一台机器

变成:

4 台服务器

那么原来的计算规则:

hash(key) % 3

就会变成:

hash(key) % 4

这时候会发生什么?


举个例子。

原来:

hash(user_1) % 3 = 1

可能在:

ServerB

现在:

hash(user_1) % 4 = 2

突然变成:

ServerC

也就是说:

几乎所有数据的位置都变了

这就是普通 Hash 最大的问题。


三、为什么这是灾难?

很多小白第一次看到这里,会觉得:

“位置变了就变了呗,有什么问题?”

实际上问题非常严重。

因为:

数据需要迁移

原来在:

ServerB

现在应该去:

ServerC

那你就得:

  • 把数据重新搬迁
  • 重新同步
  • 重新缓存

如果系统数据量非常大:

几百 GB
几 TB
甚至 PB

那么扩容一次,系统可能直接崩掉。


尤其是:

分布式缓存

比如:

  • Redis
  • Memcached

如果大量 Key 失效:

会出现:

缓存雪崩

数据库压力瞬间暴增。


于是大家开始思考:

有没有一种算法,可以在扩容时,尽量少迁移数据?

这就是:

一致性 Hash 出现的背景


四、一致性 Hash 的核心思想

一致性 Hash 最核心的思想只有一句话:

不再对机器数量取模。

而是:

把整个 Hash 空间组织成一个环。


五、什么是 Hash 环?

假设 Hash 的范围是:

0 ~ 2^32-1

正常情况下它是一条线:

0 ------------------> 2^32-1

一致性 Hash 会把它首尾连接起来:

0 -----------> 2^32-1
^               |
|_______________|

于是:

它变成了一个圆环

这就是:

Hash Ring(哈希环)


六、服务器如何放到环上?

现在我们不再用:

% 服务器数量

而是:

对服务器本身做 Hash

例如:

hash(ServerA) = 100
hash(ServerB) = 500
hash(ServerC) = 800

于是:

ServerA -> 环上的 100
ServerB -> 环上的 500
ServerC -> 环上的 800

整个 Hash 环变成:

0 ----100(A)----500(B)----800(C)----0

七、数据如何找到服务器?

现在:

假设:

hash(user_1) = 320

那么它属于谁?

一致性 Hash 的规则是:

顺时针找到第一个服务器

例如:

320 -> 顺时针找到 500(B)

于是:

user_1 -> ServerB

再比如:

hash(user_2) = 750

顺时针找到:

800(C)

于是:

user_2 -> ServerC

八、一致性 Hash 为什么能减少数据迁移?

重点来了。

假设现在新增一台服务器:

ServerD

Hash 后:

hash(ServerD) = 650

那么环会变成:

0 ----100(A)----500(B)----650(D)----800(C)----0

注意:

只有:

500 ~ 650

这部分数据会受到影响。

也就是说:

只有局部数据需要迁移

其他数据完全不动。

这就是一致性 Hash 最核心的价值。


九、普通 Hash 和一致性 Hash 的区别

普通 Hash:

机器数量变化
↓
全部重新计算
↓
大量数据迁移

一致性 Hash:

机器变化
↓
只影响相邻区间
↓
局部数据迁移

这就是为什么它叫:

一致性 Hash

因为它在节点变化时,整体映射关系仍然相对“稳定”。


十、但一致性 Hash 还有一个问题

看到这里很多人会觉得:

“这算法已经很完美了。”

但实际上:

它还有一个非常严重的问题。

数据倾斜


举个例子。

假设:

ServerA -> 100
ServerB -> 110
ServerC -> 900

环:

100(A)---110(B)--------------------900(C)

那么:

110 ~ 900

这一大段数据:

全部归 ServerC。

于是:

  • ServerC 压力巨大
  • A/B 很闲
  • 数据严重不均衡

十一、虚拟节点是怎么解决问题的?

这是很多人第一次学一致性 Hash 时最懵的地方。

其实虚拟节点非常简单。


以前:

一台服务器:

ServerA

只映射一次。

现在:

一台服务器映射很多次

例如:

ServerA#1
ServerA#2
ServerA#3
ServerA#4

它们本质上都代表:

ServerA

只是:

在 Hash 环上有多个位置

例如:

A#1 -> 100
A#2 -> 400
A#3 -> 700
A#4 -> 950

于是:

ServerA 就不再只负责一个大区间。

而是负责:

多个小区间

最终:

数据会更加均匀


十二、为什么虚拟节点效果这么好?

因为:

随机越多,越均匀

如果:

每台机器只有 1 个节点。

那么:

可能运气不好

导致分布极不均匀。

但如果:

每台机器有:

100 个虚拟节点

那么:

大数定律会让整体趋于平均

这也是为什么:

很多工程系统都会使用:

虚拟节点


十三、Redis 为什么后来不用一致性 Hash?

很多人学完一致性 Hash 后,会有一个疑问:

Redis Cluster 为什么不用一致性 Hash?

实际上:

Redis Cluster 使用的是:

Hash Slot(槽位)


Redis 固定定义:

16384 个槽

例如:

slot = CRC16(key) % 16384

然后:

把 Slot 分配给机器

这样做的好处是:

  • 更容易迁移
  • 更容易统计
  • 更容易运维
  • 更容易做主从复制

所以:

现代很多分布式系统,其实已经不直接使用一致性 Hash 了。

而是:

Slot 思想

但一致性 Hash 的思想依旧非常重要。


十四、一致性 Hash 的真实应用场景

它主要用于:


1、分布式缓存

例如:

  • Redis
  • Memcached

客户端根据一致性 Hash 找缓存节点。


2、分库分表

例如:

128 个库
每库 32 张表

很多系统会用一致性 Hash 做路由。


3、CDN

请求:

图片
视频
静态资源

根据一致性 Hash 找缓存节点。


4、对象存储

例如:

  • Cassandra
  • Ceph

十五、一句话理解一致性 Hash

如果让我只用一句话总结:

普通 Hash 解决“怎么分配数据”

一致性 Hash 解决“如何平滑扩容”


十六、最后

很多人第一次学习分布式系统时,会觉得:

一致性 Hash 很复杂。

但当你真正理解它之后,你会发现:

它本质上是在解决一个非常工程化的问题:

如何让系统在不停机的情况下,动态扩容。

而这,恰恰是现代互联网系统最核心的能力之一。

很多算法的伟大之处,并不在于数学有多复杂。

而在于:

它真正解决了现实世界的问题。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐