Redis Cluster 槽位、MOVED/ASK 重定向与水平扩缩容
更新时间:2026-09-18。当单机内存或写吞吐到顶,需要水平分片时,Redis Cluster 用「哈希槽」把数据自动切到多个 master。前置阅读:Redis 主从复制与哨兵(Cluster 的每个分片仍是一组主从)、Redis 数据结构。
本文要回答的问题
- key 怎么被分配到哪个节点?为什么是 16384 个槽?
- MOVED 和 ASK 重定向有什么区别?客户端如何处理?
- 集群元数据怎么传播?分片主从如何高可用?
- 怎么在线加节点/迁移槽?迁移过程中读写怎么走?
- 多键命令、Lua、事务为什么会报 CROSSSLOT?hash tag 怎么救?
一、哈希槽:16384 个槽映射到节点
Cluster 不做一致性哈希,而是固定 16384 个哈希槽(slot):
slot = CRC16(key) mod 16384
每个 master 负责一段槽:
node A: slots 0 ~ 5460
node B: slots 5461 ~ 10922
node C: slots 10923 ~ 16383每个 key 落槽是确定性的,节点记录的是「槽 → 负责节点」的映射表,加节点只需把部分槽迁过去,不必像取模分片那样全量重排。
# 查看槽分布
redis-cli -p 7000 CLUSTER NODES
redis-cli -p 7000 CLUSTER SLOTS
redis-cli -p 7000 CLUSTER KEYSLOT mykey # 返回该 key 的槽号
redis-cli -p 7000 CLUSTER COUNTKEYSINSLOT 123为什么是 16384? 作者给出的工程理由:gossip 消息里槽位图用 2KB 就能装下(16384 bit),心跳包小;且集群规模通常远小于此,每节点均摊槽数足够。不是拍脑袋的数字。
二、MOVED 与 ASK:两种重定向

| 重定向 | 语义 | 客户端行为 |
|---|---|---|
| MOVED | 该槽已经确定归另一个节点 | 更新本地槽映射缓存,以后直接发新节点 |
| ASK | 槽正在迁移,仅这一次重定向 | 先发 ASKING 再执行,不更新长期映射 |
迁移槽时的读规则:若 key 还在源节点就源节点处理;源节点没有则返回 ASK 让客户端去目标节点临时取。迁移完成后源节点对该槽全部返回 MOVED。
坑一:用普通 redis-cli 连单节点操作会一直收到重定向。 运维要用 redis-cli -c(自动跟随 MOVED/ASK),应用要用「集群感知」客户端(Jedis Cluster、Lettuce、redis-py cluster 等),并正确处理连接初始化时的槽表加载与运行时刷新。
三、gossip 元数据与高可用
- 每个节点持有集群拓扑(谁负责哪些槽、主从关系、存活状态),通过 gossip 协议在总线端口(客户端端口 +10000,如 7000↔17000)上交换。
- 每个分片是一主多从;master 故障由其余 master 投票判定,某个 replica 被提升接管其槽(选举/复制原理见 主从复制与哨兵)。
- 集群要求大多数 master 可达才能继续正常服务与故障转移;可用
cluster-require-full-coverage控制槽不全时是否整体拒服(默认要求全覆盖)。
CLUSTER INFO
# cluster_state:ok cluster_slots_ok:16384 cluster_known_nodes:...
# cluster_slots_assigned / cluster_slots_pfail坑二:最小可用与部署形态。 至少 3 主 3 从(每主一副本,副本分散在不同机器/机架/可用区)。多数派丢失会导致集群不可用,因此节点数、分布、网络分区都要按多数派设计。
四、在线扩缩容:reshard 迁移槽
加新节点后,把若干槽从现有节点迁过去,过程在线、逐槽、粒度细:
# 1) 加入一个新 master(先启动空节点)
redis-cli --cluster add-node 10.0.0.14:7000 10.0.0.11:7000
# 2) 规划迁移多少槽到新节点,执行 reshard(交互或参数化)
redis-cli --cluster reshard 10.0.0.11:7000 \
--cluster-from <src-id> --cluster-to <new-id> \
--cluster-slots 2048 --cluster-yes
# 3) 检查均衡与完整性
redis-cli --cluster check 10.0.0.11:7000
redis-cli --cluster rebalance 10.0.0.11:7000单槽迁移的内部步骤:
目标节点 IMPORTING <slot>
源节点 MIGRATING <slot>
逐个 key:CLUSTER GETKEYSINSLOT + MIGRATE(迁移中产生 ASK)
迁移完:两节点更新槽归属并经 gossip 广播 -> 后续变 MOVED缩容则反向:先把节点上的槽 reshard 到其他节点,再 del-node 下线。
坑三:大 key / 热 key 在 reshard 时被 MIGRATE 整 key 搬移,可能造成延迟毛刺。 迁移前应先治理 BigKey/HotKey(见 BigKey/HotKey 诊断),避免单个巨型 hash/zset 的迁移阻塞;迁移也应在低峰进行。
五、跨槽限制:多键、事务、Lua 与 hash tag
Cluster 按 key 路由,凡是一次操作涉及多 key 的,都要求这些 key 在同一槽,否则报 CROSSSLOT Keys in request don't hash to the same slot:多键命令(MGET、DEL a b、批量混合 key)、MULTI/EXEC、Lua 脚本里的所有 key。
hash tag:key 中 {...} 包裹的部分决定槽,让相关 key 强制同槽:
# 用户 1001 的资料/购物车/订单缓存同槽,可在一个 Lua/事务里操作
SET user:{1001}:profile ...
HSET cart:{1001} sku 1
LRANGE orders:{1001} 0 -1
# CLUSTER KEYSLOT user:{1001}:profile == CLUSTER KEYSLOT cart:{1001}| 场景 | 做法 |
|---|---|
| 必须原子/一起操作的一组 key | 相同 hash tag 聚同槽 |
| 无关 key 批量取 | 客户端按槽分组并发(smart client 自动) |
| 全局聚合/统计 | 应用层归并或独立聚合存储 |
坑四:滥用 hash tag 制造热点分片。 把大商户/热门实体都聚到同一 tag 等于放弃该部分负载均衡,reshard 也拆不开同槽 key。只对确需同槽原子的小集合使用并控制体量。
六、Sentinel 与 Cluster 选择
| 维度 | 主从 + Sentinel | Redis Cluster |
|---|---|---|
| 目标 | 高可用 | 高可用 + 水平扩展 |
| 数据量 | 单 master 全量 | 按槽分散多 master |
| 客户端 | Sentinel 发现主 | 必须集群感知、处理 MOVED/ASK |
| 跨键原子 | 无限制 | 仅同槽(hash tag) |
| 运维 | 较简单 | reshard/槽均衡更复杂 |
单机放得下、只要自动故障转移选 Sentinel;内存/写吞吐到单机瓶颈、要水平扩容才上 Cluster。
七、相关与延伸
分片内主从复制/故障转移见 Redis 主从复制与哨兵;全量同步与持久化见 RDB 与 AOF;迁移前要处理的大键热点见 BigKey/HotKey 诊断;缓存一致性与故障模式见 缓存一致性 与 穿透/击穿/雪崩。
总结
Redis Cluster 槽位与扩缩:用固定 16384 个哈希槽(slot=CRC16(key)%16384)映射到多 master,加节点只迁移槽不全量重排,拓扑靠 gossip 在总线端口传播、每分片一主多从由 master 投票故障转移;客户端按槽直连,确定变更收 MOVED(更新映射)、迁移中收 ASK(先发 ASKING、不更新映射),用 redis-cli --cluster reshard/rebalance 在线逐槽迁移(MIGRATE 大 key 有毛刺,先治 BigKey);多键命令、MULTI、Lua 要求多 key 同槽否则 CROSSSLOT,可用 hash tag 强聚同槽但要防热点;单机放得下只需高可用选 Sentinel,要水平分片才上 Cluster。