外观
避坑实录:Redis Cluster 在线扩容 slot 迁移为何中断请求
问题背景:在线扩容不是只加几台机器
Redis Cluster 通过 16384 个 hash slot 把数据分散到多个主节点。业务量增长后,常见操作是加入新节点并把一部分 slot 从旧节点迁过去。理论上,Redis Cluster 支持在线扩容,客户端只需处理 MOVED 与 ASK 重定向;但在真实生产环境中,slot 迁移经常伴随短暂报错、连接抖动、热点 key 超时,甚至出现业务线程被重定向放大拖慢的情况。
典型现象是:执行 redis-cli --cluster reshard 后,旧节点 CPU、网络突然升高;应用日志出现 TRYAGAIN、ASK、MOVED;部分使用 MULTI、Lua 或 pipeline 的请求失败;迁移大 key 时命令长时间卡住。很多团队把问题简单归结为“客户端不支持集群模式”,但根因往往涉及 slot 状态、迁移批次、客户端连接池、key 大小和跨 slot 操作。
本文从一次在线扩容故障出发,讲清楚 slot 迁移期间请求为什么会中断,如何用可运行命令安全扩容,以及生产环境必须避开的坑。
slot 迁移期间请求为什么会受影响
Redis Cluster 的每个主节点负责一部分 slot。正常情况下,客户端可以缓存 slot 到节点的映射;当 slot 所属节点变化后,节点返回 MOVED,客户端刷新本地路由表即可。
迁移中的状态更特殊。源节点会把某个 slot 标记为 MIGRATING_TO,目标节点标记为 IMPORTING_FROM。此时:
- key 不在源节点:源节点返回 ASK,要求客户端临时访问目标节点;
- key 在目标节点:目标节点只接受带
ASKING的临时请求; - 迁移完成并执行
CLUSTER SETSLOT NODE后,集群元数据更新,之后才返回 MOVED; - 若多个节点的 epoch、配置不一致,可能出现客户端在 ASK 与 MOVED 之间反复跳转。
因此,ASK 是迁移过程中的临时重定向,MOVED 是迁移完成后的永久路由变化。只支持单机模式、未开启集群支持的客户端,通常无法自动处理这两种响应。
扩容方案对比:一次性 reshard 并不总是最优
常见扩容方案有三类。
第一类是 redis-cli --cluster reshard 交互式迁移。优点是官方工具、门槛低、适合少量 slot;缺点是交互参数容易误填,且默认按 key 数量迁移,不考虑字节大小和热点分布。
第二类是自动化脚本分批迁移。每次只迁移少量 slot,每批之间检查集群状态、复制延迟和业务指标,适合生产环境。
第三类是业务低峰窗口配合代理层摘流。先在接入层降低旧节点流量,再迁移热点 slot,最稳妥,但需要发布和流量调度能力。
如果集群中存在大 hash、大 zset、超长 list,不能只看 key 数量。一个 200MB 的 hash 与几万个小 key 对阻塞和网络的影响完全不同。扩容前应先用抽样或离线扫描评估 key 大小,再决定批次。
实操步骤:可回滚的 Redis Cluster 在线扩容
下面示例使用 7001 旧节点、7010 新主节点、7011 新副本。命令可在本地多实例测试环境直接执行,生产环境替换地址即可。
1. 启动新节点并加入集群
bash
# 启动新主节点,配置 cluster-enabled yes
redis-server --port 7010 --cluster-enabled yes \
--cluster-config-file nodes-7010.conf \
--cluster-node-timeout 5000 \
--dbfilename dump-7010.rdb \
--daemonize yes
# 启动新副本节点
redis-server --port 7011 --cluster-enabled yes \
--cluster-config-file nodes-7011.conf \
--cluster-node-timeout 5000 \
--dbfilename dump-7011.rdb \
--daemonize yes
# 将 7010 作为主节点加入已有集群
redis-cli --cluster add-node 127.0.0.1:7010 127.0.0.1:7001
# 查看节点 ID
redis-cli -p 7010 cluster myid
# 把 7011 作为 7010 的副本,NODE_ID 替换为上一步输出
redis-cli --cluster add-node --cluster-slave \
--cluster-master-id NODE_ID \
127.0.0.1:7011 127.0.0.1:7001加入后先检查集群是否仍为 ok,不要立刻迁移:
bash
redis-cli --cluster check 127.0.0.1:7001
redis-cli -p 7010 cluster info
redis-cli -p 7011 info replication2. 观察 slot、key 大小和热点
bash
# 查看各节点负责的 slot
redis-cli -p 7001 cluster slots
# 查看节点总体内存
redis-cli -p 7001 info memory | grep used_memory_human
# 抽样查看大 key,生产环境建议在从节点执行,避免影响主线程
redis-cli -p 7001 --bigkeys
# 观察实时命令热点,确认是否有高频访问的 slot
redis-cli -p 7001 --hotkeysRedis Cluster 的 slot 计算公式是 CRC16(key) mod 16384。如果 key 包含 hash tag,例如 user:{1001}:profile 与 order:{1001}:list,它们会落到同一个 slot。这样可以支持多 key 事务,但也容易形成热点和大 slot。
3. 小批量执行 reshard
下面把 100 个 slot 从 7001 迁到 7010。生产环境建议一批 20 到 100 个 slot,而不是一次迁几千个。
bash
SRC_ID=$(redis-cli -p 7001 cluster myid)
DST_ID=$(redis-cli -p 7010 cluster myid)
redis-cli --cluster reshard 127.0.0.1:7001 \
--cluster-from "$SRC_ID" \
--cluster-to "$DST_ID" \
--cluster-slots 100 \
--cluster-yes \
--cluster-timeout 10000 \
--cluster-pipeline 10参数含义要特别注意:--cluster-timeout 是迁移命令超时时间,不是客户端请求无感知的保证;--cluster-pipeline 控制一次批量迁移的 key 数量,过大可能抬高源节点延迟。
4. 手动理解 slot 迁移命令
自动工具底层主要使用以下命令。理解它们有助于判断卡在什么阶段:
bash
# 目标节点准备导入 slot 1234
redis-cli -p 7010 CLUSTER SETSLOT 1234 IMPORTING SRC_ID
# 源节点标记 slot 1234 正在迁出
redis-cli -p 7001 CLUSTER SETSLOT 1234 MIGRATING DST_ID
# 迁移普通 key,COPY 后 REPLACE,AUTH 按需填写
redis-cli -p 7001 MIGRATE 127.0.0.1 7010 "" 0 10000 \
KEYS user:1001 user:1002
# 所有节点确认 slot 归属,必须在所有主节点执行
redis-cli -p 7001 CLUSTER SETSLOT 1234 NODE DST_ID
redis-cli -p 7010 CLUSTER SETSLOT 1234 NODE DST_ID迁移中的 key 状态也可以检查:
bash
# 查看正在迁入、迁出的 slot
redis-cli -p 7001 CLUSTER COUNTKEYSINSLOT 1234
redis-cli -p 7001 CLUSTER GETKEYSINSLOT 1234 10
# 查看集群状态,fail 或 handshaking 节点需要先处理
redis-cli -p 7001 CLUSTER INFO5. 验证客户端行为
使用 redis-cli -c 可以模拟集群客户端自动跟随重定向:
bash
# -c 表示支持集群重定向
redis-cli -c -p 7001
127.0.0.1:7001> SET user:1001 "tom"
OK
127.0.0.1:7001> GET user:1001
-> Redirected to slot #1234 located at 127.0.0.1:7010
"tom"如果不带 -c,客户端只会看到 ASK 或 MOVED 错误,不会自动跳转。很多业务报错本质上就是连接配置写成了单机地址,或客户端未启用 cluster mode。
中断请求的常见根因
第一,客户端不支持重定向或路由缓存刷新不及时。旧连接仍访问源节点,单 key 请求可能靠 MOVED 恢复,但连接池中的旧配置、DNS 代理或四层 VIP 可能让刷新失效。
第二,迁移中执行跨 slot 的多 key 操作。例如 MGET user:1 user:2、Lua 脚本、事务涉及多个 slot。Redis Cluster 要求多 key 命令位于同一 slot,迁移状态下即使使用 hash tag,也可能因为临时 ASK 访问而失败。
第三,大 key 迁移造成长时间阻塞和网络抖动。MIGRATE 会迁移完整 value,超大 hash、list、zset 会占用 CPU 与网络;目标节点恢复数据也会消耗内存。
第四,pipeline 批次过大。工具为了提速会批量取 key 并迁移,但源节点同时承担线上读写时,过高 pipeline 会放大延迟,应用端看到的就是超时。
第五,主从切换与 slot 迁移重叠。迁移期间节点超时、网络抖动触发 failover,可能导致 slot ownership 更新不一致,严重时需要人工修复路由。
避坑总结:生产环境至少检查这些点
避坑 1:不要在业务高峰一次迁移大量 slot
常见错误现象:执行 reshard 后 P99 延迟从 2ms 飙到几百毫秒,日志大量超时。修正方法是按小批次迁移,每批 20 到 100 个 slot,每批后检查 CLUSTER INFO、主从复制延迟、应用错误率和网络带宽。热点 slot 应放在低峰或摘流后迁移。
避坑 2:不要忽略大 key 和 hash tag 热点
错误现象:大部分 slot 很快完成,某个 slot 长时间卡住,源节点出网带宽打满。修正方法是迁移前在从节点执行 --bigkeys,对大 hash、大 zset 先做业务拆分或瘦身;对固定 hash tag 带来的热点,要按业务前缀分散,例如把 {tenant} 改为 {tenant:shard}。
避坑 3:不要让不支持集群的客户端直连节点
错误现象:应用持续报 MOVED 或拿到错误文本而不是值。修正方法是使用支持 Redis Cluster 的客户端并填写多个种子节点,开启拓扑刷新;不要只配置单个节点 IP,也不要在集群客户端前面随意加不理解 MOVED 的普通代理。
避坑 4:不要在迁移窗口执行跨 slot 事务和 Lua
错误现象:平时偶发的 CROSSSLOT 在扩容期间变成大量失败,或事务中出现 ASK 后未带 ASKING。修正方法是提前审查多 key 命令,用 hash tag 保证相关 key 同 slot;无法改造的脚本应避开迁移窗口,或在业务层增加重试与降级。
避坑 5:不要在集群异常时强行完成 SETSLOT
错误现象:有节点处于 fail、handshaking 或副本复制中断,仍继续执行迁移,最后 slot 映射不一致。修正方法是先 redis-cli --cluster check 修复节点状态,确认所有主节点健康、副本同步正常,再继续。手工迁移时,CLUSTER SETSLOT ... NODE 必须在所有主节点达成一致。
避坑 6:不要把 ASK 当成永久路由缓存
错误现象:客户端把 ASK 返回的目标节点长期缓存,迁移完成后仍访问旧目标,出现反复失败。修正方法是遵循协议语义:ASK 只对当前一次命令有效,并且下一次请求要先发 ASKING;只有 MOVED 才能刷新本地 slot 路由表。
结语
Redis Cluster 在线扩容的关键,不是把节点加进集群,而是控制 slot 迁移对线上读写的影响。可靠路径是:迁移前摸清 slot、大 key、热点和客户端能力;迁移时小批量、低峰值、可观测;迁移后验证 MOVED 路由、主从复制和业务错误率。只要理解 ASK 与 MOVED 的差异,并避开大 key、跨 slot 操作和异常集群状态,slot 迁移完全可以做到平滑可控。
免责声明
本工具与文章内容仅用于学习测试场景,优化建议仅供参考,请勿直接用于生产环境,由此产生的风险自行承担。