连载中 13/16

Cluster 集群:数据大了怎么切,哈希槽怎么算

2026-05-21 · 2053 阅读 · 0 评论 · 0 赞

内存条加到头了,然后呢

哨兵上线后,缓存安稳了半年。直到某天容量报表递到面前:Redis 实例 60G,机器内存 64G——内存条加到头了。这时候才明白第 12 篇结尾那句话的分量:哨兵救的是「挂」,救不了「装不下」。主从模式里每个节点都存着全量数据,加再多从库也只是副本翻倍,容量纹丝不动。想让容量横向扩,只有一条路:把数据切开,分片存储——这就是 Cluster 集群。

分片的核心问题只有一个:一条数据来了,该放进哪台机器?接下来就沿着这个问题的三个候选答案,看 Redis 作者 antirez 为什么最终选了「哈希槽」。

三个候选答案

方案扩缩容时的影响倾斜风险
hash(key) % NN 变了,几乎全部 key 要搬家
一致性哈希只有相邻区间的 key 迁移节点在环上分布不均时倾斜
哈希槽只搬被移走的槽,粒度可控槽位规划明确,风险可控

取模方案的死穴在扩容:3 台变 4 台,取模结果几乎全部变化,等于数据大迁徙。一致性哈希用「环」缓解了这个问题,但环上节点位置随机,容易倾斜,还得引入虚拟节点来匀。antirez 的答案是多加一层间接:固定 16384 个哈希槽,key 先映射到槽,槽再分配给节点。槽是死的、可枚举的,扩缩容变成「搬几个槽」的精细操作,数据分布从此有了账本。

槽怎么算:CRC16 与 hash tag

每个 key 落在哪个槽,由 CRC16(key) mod 16384 决定,简单到没有悬念。但这里藏着 Cluster 模式最实用的技巧——hash tag:key 中花括号里的内容才参与哈希计算。

# 普通键:user:1001 和 order:1001 大概率不在同一槽
# hash tag:花括号里的内容参与计算,强制落到同一槽
mset {order:1001}:detail xx {order:1001}:items yy

为什么需要它?Cluster 的多 key 命令、事务、Lua 脚本都要求所有 key 落在同一个槽。一个订单的详情、明细、状态如果能用同一个 tag 圈起来,业务代码几乎不用改造。顺带回答那个经典问题——为什么是 16384 而不是 65536:节点之间要用心跳交换「我负责哪些槽」的位图,16384 个槽的位图是 2KB,65536 就是 8KB;而官方建议的集群规模(1000 节点以内)用不到 65536 个槽,作者干脆取了小号,省带宽。

去中心化:MOVED 与 smart client

Cluster 没有中心节点,每个节点都知道全集群的槽位分布,节点之间用 gossip 协议互相同步状态。你直接给任意节点发命令,试试会发生什么:

# 请求发到了不负责这个槽的节点
get user:1001
# (error) MOVED 9189 10.0.0.2:6379

MOVED 重定向的意思是:这个槽归 10.0.0.2 管,你去问它。客户端要是每次都吃一次重定向,性能就没法看了——所以成熟的客户端都是 smart client:启动时拉一份「槽位→节点」映射表缓存在本地,请求直接打对节点,只有拓扑变化时才重新拉取。Java 里用 Lettuce 只需给种子节点:

// Cluster 模式:给种子节点,客户端自动发现全部拓扑
RedisClusterClient client = RedisClusterClient.create(
        RedisURI.create("redis://10.0.0.1:6379"),
        RedisURI.create("redis://10.0.0.2:6379"),
        RedisURI.create("redis://10.0.0.3:6379"));

扩缩容:搬槽进行时

加节点的流程由 redis-cli --cluster reshard 一键完成:从现有节点匀一批槽给新节点。真正有趣的是迁移过程中的语义——被迁移的槽里的 key 正在半路上,此时访问:源节点返回 ASK 重定向(这次去问目标节点,但映射表别改),目标节点确认后就绪后,后续访问才变成正式的 MOVED。ASK 和 MOVED 的区别,就是「临时指路」和「正式改地址」的区别。缩容反过来:先把槽搬空,再下线节点。

最后是一份「上了 Cluster 要改的习惯」清单:

  • 多 key 操作先想槽:mget、事务、Lua 都要求同槽,跨槽就把批量拆成单发,或用 hash tag 规划 key。
  • 只有 db0:Cluster 模式不支持多数据库,依赖 select 1 的老代码要改。
  • 高可用是自带的:每个主节点配一个从节点,主挂了从自动顶上,不需要也不应该再叠哨兵——Cluster 自己会选主,别把它和哨兵混着用。
  • 规模别超配:官方建议 1000 节点以内,gossip 同步和槽位管理的成本会随节点数上涨,多数业务几十个节点绰绰有余。

总结:哈希槽的本质是在 key 和节点之间垫了一层可枚举的中间层,扩缩容从「数据大迁徙」变成「搬槽」;hash tag 解决多 key 同槽,smart client 消化重定向,高可用自带免哨兵。到这里,Redis 的架构三部曲(主从、哨兵、集群)就讲完了。下一篇回归日常编码——性能优化:为什么 keys 命令是生产毒药?Pipeline 怎么把一万次网络往返压成一次?慢查询日志又该怎么读?下篇见。

咖啡凉了,记得趁热喝。

503

10 年全栈工程师 · 503咖啡馆主理人

#Redis#Cluster#哈希槽#分片#数据分片

评论 (0)

相关推荐

连载中 12/20

排查四件套:jstack、jmap、jstat、jcmd 的实战分工

jstack 看线程在干什么,jmap 看堆里装了什么,jstat 看运行时在变什么,jcmd 是统一入口。四把刀各管一段,配合着用没有查不动的现场。

#jstack#jmap#jstat#jcmd#排查工具
2026-09-16 · 5 阅读 · 0 评论 · 0 赞
连载中 11/20

GC 日志:把回收过程翻译成人话

一行 GC 日志里塞着七种信息:谁触发的、收了哪、停了多久、活了哪些。加上 -Xlog 配置,再加上日志分析工具,GC 不再是只能盯监控曲线的黑盒。

#GC日志#Xlog#日志分析#GC监控#Full GC排查
2026-09-16 · 13 阅读 · 0 评论 · 0 赞
连载中 10/20

ZGC:亚毫秒停顿是怎么炼成的

百 G 大堆停顿不到一毫秒,靠的是把搬家全部挪到并发阶段——着色指针让引用自带状态,读屏障让搬运中的对象依然可访问。代价是吞吐与内存,收益是停顿与堆大小解耦。

#ZGC#着色指针#读屏障#亚毫秒停顿#分代ZGC
2026-09-15 · 8 阅读 · 0 评论 · 0 赞