Redis 集群分区思想的演进:从哈希取余到虚拟槽

在分布式缓存系统中,如何将海量数据均匀地分配到多个节点上,并在节点扩缩容时保持系统的平滑稳定,是一个核心难题。Redis 集群的分区方案正是为了解决这一问题而不断演进的。本文将带你梳理从普通哈希取余、一致性哈希到 Redis 虚拟槽分区的完整思想脉络。

第一阶段:普通哈希取余(Hash Modulo)

最朴素的分区思想是普通哈希取余 。其核心算法非常直接:通过对数据的 Key 进行哈希计算,再对节点总数 N 取余,从而得出数据应该存放的节点位置。

公式表示为:Node_Index = hash(key) % N

致命缺陷:扩缩容灾难

这种算法的致命弱点在于对节点数量的强依赖。当集群进行扩缩容(N 发生变化)时,绝大多数数据的映射结果都会发生改变。

  • 全量迁移:除非节点数量严格按照倍数变化(例如从 N 扩容到 2N),否则几乎所有的 Key 都需要重新计算位置,导致灾难性的全量数据迁移。
  • 缓存雪崩:在缓存系统中,这意味着后端数据库瞬间面临巨大的访问压力。

举个直观的例子:

假设我们一开始有 3 台机器(N=3),此时有 3 个数据 Key,它们经过哈希计算后的原始值分别是 10、11、12。

  • 10 % 3 = 1(存放在第 1 号机器)
  • 11 % 3 = 2(存放在第 2 号机器)
  • 12 % 3 = 0(存放在第 0 号机器)
    此时数据分布非常均匀。

但是,当我们为了扩容,增加了 1 台机器 ,总机器数变成了 4 台(N=4)。这时候再对同样的 3 个 Key 进行计算:

  • 10 % 4 = 2(原本在 1 号机,现在要搬到 2 号机)
  • 11 % 4 = 3(原本在 2 号机,现在要搬到 3 号机)
  • 12 % 4 = 0(原本在 0 号机,现在依然在 0 号机)

你会发现,仅仅增加 1 台机器,这 3 个数据中有 2 个(约 66%)都需要发生迁移。在实际生产环境中,如果有几百万个 Key,只要 N 稍微变动,超过 70%~90% 的数据都会失效并需要重新搬运,这会导致整个系统在扩容期间几乎瘫痪。

第二阶段:一致性哈希(Consistent Hashing)

为了解决普通哈希取余的僵化问题,一致性哈希应运而生。它引入了一个 0~2^32 的哈希环概念:

  1. 节点映射:对每个物理节点进行哈希计算,将其映射到哈希环的某个位置上。
  2. 数据映射:对数据的 Key 进行哈希计算,同样映射到环上。
  3. 顺时针查找:数据沿着环顺时针行走,遇到的第一个物理节点,就是它要存储的目标节点。

改进与遗留问题

一致性哈希在扩缩容时,只会影响环上相邻节点的一小部分数据,极大地减少了迁移量。但它依然存在数据倾斜的风险:如果物理节点在环上分布不均匀(例如 Node A 和 Node B 靠得很近,而 Node C 离得很远),Node C 就要负责环上一大段区间的数据。

举个直观的例子:

想象一个时钟表盘(哈希环),Node A 在 12 点位置,Node B 在 1 点位置,而 Node C 远在 6 点位置。

  • 落在 1 点到 6 点之间的所有数据,顺时针走遇到的第一个节点都是 Node C。这意味着 Node C 承担了表盘上一大半的数据压力。
  • 此时如果在 5 点位置新增一个 Node D,那么原本属于 Node C 的大部分数据(1 点到 5 点之间的数据)会瞬间全部被 Node D 抢走。Node C 瞬间变闲,Node D 瞬间爆满,这就是严重的负载不均。
第三阶段:虚拟节点(Virtual Nodes)

为了解决一致性哈希的数据倾斜问题,虚拟节点 的概念被提出。

它的核心思想是将一个物理节点映射成成百上千个"虚拟节点",并将这些虚拟节点均匀且随机地撒在哈希环上。

  • 打散分布:通过虚拟节点,物理节点在环上的分布变得极其均匀。
  • 平滑迁移:当新增一个物理节点时,它对应的多个虚拟节点会均匀地插入到环的各个角落。新节点会从集群中各个原有的物理节点那里,"公平地"分摊走一小部分数据,而不是只盯着某一个节点"薅羊毛"。

举个直观的例子:

还是刚才的表盘,现在 Node A 不再只是 12 点的一个点,而是变成了 A1、A2、A3...A100 这一百个分身,均匀地撒在表盘的每一个刻度上。Node B 和 Node C 同理。

此时,整个表盘被密密麻麻的虚拟节点瓜分得非常均匀。当你新增一个物理节点 D(带着它的 100 个虚拟分身)时,这 100 个分身会均匀地插在表盘各处,从 A、B、C 的每一个地盘里都"切走"一小块蛋糕。这样,所有旧节点的负载都均匀地下降了一点点,完美实现了平滑扩容。

为了更直观地对比这三种思想的差异,我们可以通过下表进行总结:

分区算法 核心思想 扩缩容表现 缺点与改进
普通哈希取余 hash(key) % N 节点数非倍数变动时,需全量迁移数据 扩展性极差,缓存容易集体失效
一致性哈希 哈希环 + 顺时针查找 仅影响相邻节点的一小部分数据 节点少时易产生数据倾斜(热点)
带虚拟节点的一致性哈希 物理节点映射为多个虚拟节点 新节点从所有旧节点均匀分摊数据 增加了少量的元数据维护成本
第四阶段:Redis 虚拟槽分区(Virtual Slot Partitioning)

Redis Cluster 并没有直接沿用一致性哈希,而是提出了一种更高效的虚拟槽分区思想。它引入了"槽位(Slot)"作为数据与物理节点之间的中间层。

核心机制:16384 个槽位

Redis 集群固定了 16384 个槽位(编号 0~16383)。数据的映射公式为:HASH_SLOT = CRC16(key) mod 16384

  • 轻量级元数据:16384 个槽位用 Bitmap(位图)表示,刚好占用 2KB 的内存。这意味着在集群内部通过 Gossip 协议同步槽位与节点的映射关系时,几乎不会占用网络带宽。
  • 数据与槽位强绑定:无论集群如何扩缩容,某个 Key 属于哪个槽位是永远不变的。

Redis 集群扩容的底层逻辑

当集群扩容时,Redis 并不是把所有数据拿出来重新洗牌,而是改变"槽位与节点"的对应关系:

  1. 划拨管辖权:从现有的旧节点中,匀出一部分槽位指派给新节点。
  2. 物理迁移:把这部分槽位里包含的实际数据(Key-Value),从旧节点物理迁移到新节点上。
  3. 保持绑定:原本在第 5000 号槽位的数据,迁移后依然待在 5000 号槽位里,只是存放它的机器变了。

举个直观的例子:

假设集群原本有 3 个节点,平均分配 16384 个槽位:

  • 节点 A 负责:0 ~ 5460 号槽位
  • 节点 B 负责:5461 ~ 10922 号槽位
  • 节点 C 负责:10923 ~ 16383 号槽位

现在我们要加入节点 D。Redis 会从 A、B、C 中各自匀出一部分槽位(比如各匀出 1/4)给 D。最终变成:

  • 节点 A 负责:0 ~ 4095 号槽位
  • 节点 B 负责:5461 ~ 9555 号槽位
  • 节点 C 负责:10923 ~ 15017 号槽位
  • 节点 D 接手 :40965460、955610922、15018~16383 这些槽位。

在这个过程中,假设有一个 Key 算出来是 1000 号槽位,它原本在 A 上,扩容后依然在 A 上(因为 A 还管 0~4095),完全不需要动。假设另一个 Key 算出来是 5000 号槽位,它原本在 A 上,现在只需要带着数据搬到 D 上即可。这种"按槽位搬家"的方式,既保证了数据的一致性,又实现了极致的平滑扩容。

为了清晰展示 Redis 集群在扩容时的数据与槽位交互流程,我们可以通过以下时序图来理解:

客户端路由与智能更新

Redis 集群采用的是智能客户端直连模式。客户端在第一次连接集群时,会获取并缓存"槽位-节点映射表"到本地。在日常访问中,客户端会根据公式算出 Key 的槽位,查表后直接一步到位地向目标节点发送请求。

如果在扩容期间,客户端拿着过期的映射表把请求发给了旧节点,旧节点会返回一个带有正确地址的重定向错误。客户端收到后会立刻更新本地的映射表,下次就能精准找到数据。

重定向类型 触发场景 客户端如何处理
MOVED 槽位已经永久迁移到了新节点(如扩容完成) 更新本地缓存的映射表,下次直接找新节点
ASK 槽位正在迁移中(处于中间过渡状态) 临时去找目标节点,但不更新本地缓存

通过引入"虚拟槽"这个中间层,Redis 完美解耦了"数据"和"物理节点"的直接关系。扩缩容时,只需要像搬运集装箱一样,把一个个装着数据的"槽位集装箱"整体挪到新的卡车上即可,既保证了数据的一致性,又实现了极致的平滑扩容。

相关推荐
努力努力再努力wz17 小时前
【Redis入门系列】:从 hashtable到 listpack:深入理解 Hash 底层编码、字段级过期、核心命令与缓存应用
开发语言·数据结构·数据库·c++·redis·算法·缓存
止语Lab17 小时前
从 sync.Map 到 Redis:Go 缓存升级的三个拐点
redis·缓存·golang
yh弓长17 小时前
Redis的基础指令
redis
洛水水17 小时前
redis缓存:雪崩、穿透、击穿详解
数据库·redis·缓存
洛水水17 小时前
Redis 内存淘汰策略详解
数据库·redis·缓存
yh弓长18 小时前
Redis的string类及基础指令
数据库·redis·缓存
csjane107918 小时前
Redis 分布式锁实战
java·redis
それども18 小时前
redis 集群操作进阶 - hashtag
数据库·redis·缓存
Devin~Y18 小时前
大厂Java面试实录:Spring Boot/Cloud、Redis+Kafka、JVM调优与RAG/Agent(Spring AI)三轮递进问答
java·jvm·spring boot·redis·spring cloud·kafka·rag