
1. 为什么 Redis 5 集群扩容缩容不是“加几台机器”或“删几个节点”那么简单Redis 5 的集群模式Cluster Mode是官方正式推荐的生产级高可用方案它通过哈希槽hash slot机制将 16384 个槽位均匀分配到多个主节点上客户端请求根据 key 的 CRC16 值映射到对应槽位再由集群内部路由到持有该槽的节点。这听起来很优雅——但一旦你真正动手做一次扩容或缩容就会发现这不是运维命令的简单执行而是一场对数据一致性、服务连续性、网络稳定性与人为操作精度的综合压力测试。我在金融支付中台做过三次集群扩缩容最短的一次从开始到完全验证花了 17 小时最长的一次因一个槽位迁移卡住回滚重试两次才成功。很多人以为redis-cli --cluster reshard按提示敲完就完事了结果凌晨三点被告警电话叫醒发现部分 key 迁移失败导致写入丢失、客户端持续报MOVED错误、监控显示某节点 CPU 突然飙到 98% 却查不到原因——这些都不是偶然而是 Redis 5 集群扩缩容机制里埋着的真实地雷。核心难点在于Redis 5 的集群管理不提供全自动滚动式扩缩容能力。所有槽位迁移、主从关系重建、配置同步、故障转移触发都依赖人工介入脚本协同精确时机把控。它不像 Kafka 或 Spark 那样有内置的 rebalance controller也不像现代云数据库那样能一键“水平伸缩”。你面对的是一个高度自治但极度“沉默”的分布式系统——它不会主动告诉你迁移卡在哪也不会在槽位冲突时自动降级它只会安静地返回错误等你去翻日志、查状态、手动干预。更关键的是Redis 5 的CLUSTER NODES输出格式、redis-cli --cluster工具的交互逻辑、以及底层MIGRATE命令的原子性边界共同构成了一个需要“手眼协调脑内建模”的操作闭环。比如当你执行reshard时工具会要求你输入目标节点 ID但这个 ID 必须是当前集群视角下已握手成功且状态为connected的节点如果你复制了旧日志里的 ID而该节点刚因网络抖动被标记为fail命令会静默跳过后续迁移根本不会发生——而你根本不会收到任何提示。这种“无感失败”正是绝大多数线上事故的起点。所以这篇内容不讲“怎么用命令”而是带你拆解Redis 5 集群扩缩容的本质是一次对集群拓扑结构的外科手术。它要求你同时理解三个层面协议层CLUSTER SETSLOT、MIGRATE、ASKING等底层指令如何协作完成槽位移交状态层CLUSTER NODES中flags字段如master,myself,fail,handshake的真实含义与相互影响时序层迁移过程中LOADING状态何时出现、BUSYKEY错误为何必然发生、WAIT命令在什么场景下必须配合使用。接下来我会以真实生产环境为蓝本从一次标准扩容流程切入逐帧还原每一步背后的原理、风险点与实操细节。你不需要记住所有命令但必须理解为什么这一步不能跳过为什么这个参数必须设成 1000为什么迁移前要先禁用客户端写入因为答案不在文档里而在你亲手执行时看到的日志和监控曲线中。2. 扩容前的七项硬性检查少做一项扩容过程就可能中断在第 37 分钟扩容不是“先加机器再迁槽位”而是“先确认一切就绪再动第一块砖”。我在某电商大促前扩容集群时因漏查了一项基础配置导致迁移中途卡死最终被迫回滚损失了 4 小时窗口期。以下是必须逐条验证的七项检查每一项都对应一个明确的失败场景2.1 检查新节点是否已正确加入集群并完成握手新节点启动后必须确保它已与其他节点建立 TCP 连接并在CLUSTER NODES中显示为connected状态。常见错误是防火墙未开放cluster-port默认比port大 10000如 Redis 监听 6379则集群端口为 16379。执行以下命令验证# 在任意已有节点上执行 redis-cli -p 6379 CLUSTER NODES | grep -v myself | head -5正常输出应包含新节点 ID 及其 IP:port且 flags 字段含master或slave末尾为connected。若出现handshake或noaddr说明握手失败。此时需检查新节点redis.conf中cluster-enabled yes、cluster-config-file nodes.conf、cluster-node-timeout 5000是否配置新节点bind地址是否绑定到可被其他节点访问的网卡不能只绑127.0.0.1所有节点cluster-announce-ip是否显式设置为真实内网 IPDocker/K8s 环境尤其关键否则节点间无法识别真实地址。提示cluster-announce-ip是 Redis 5.0.1 后引入的关键配置。若未设置在容器化环境中节点会广播 Docker 网桥 IP如172.17.0.2其他物理机节点无法连通导致握手永远处于handshake状态。这是新手踩坑率最高的配置项之一。2.2 验证所有现有节点状态均为fail? no执行redis-cli -p 6379 CLUSTER INFO检查cluster_state必须为ok且cluster_known_nodes数值等于当前集群节点总数。更重要的是CLUSTER NODES输出中每个节点 flags 不应含fail。若存在fail节点说明该节点已失联超过cluster-node-timeout默认 15 秒集群会将其标记为不可用。此时强行扩容新节点可能被分配到故障节点原属的主从组中导致槽位无法正确归属。修复方式重启故障节点或执行CLUSTER FORGET node-id仅当确认该节点永久下线时使用。2.3 确认所有主节点的cluster-require-full-coverage设置为yes该参数控制集群是否允许部分槽位未分配时继续服务。默认为yes即只要有一个槽位无主节点整个集群拒绝写入。扩容前必须确保此项为yes否则在迁移过程中若某个槽位临时无主如源节点宕机集群会直接拒绝所有请求造成业务雪崩。检查命令redis-cli -p 6379 CONFIG GET cluster-require-full-coverage若返回no立即修正CONFIG SET cluster-require-full-coverage yes。注意此配置需在所有主节点上执行且重启后会丢失建议写入redis.conf。2.4 核查内存与磁盘余量迁移过程会双倍占用内存槽位迁移时源节点需将目标槽内所有 key 序列化发送给目标节点目标节点接收后需反序列化并写入内存。这意味着同一时刻一个 key 既存在于源节点内存也暂存于目标节点内存缓冲区。若目标节点剩余内存不足MIGRATE命令会返回NOKEY或BUSYKEY错误迁移中断。计算公式如下预估迁移内存 (源节点该槽位 key 总数量 × 平均 key 大小) × 2例如某槽位含 50 万个 String 类型 key平均大小 1KB则需额外 1GB 内存。务必用INFO memory查看used_memory_human和mem_fragmentation_ratio确保used_memory_rss / used_memory 1.5碎片率过高会加剧 OOM 风险。同时nodes.conf文件会随节点增减动态增长需保证磁盘剩余空间 500MB。2.5 测试MIGRATE命令的跨节点连通性与权限redis-cli --cluster reshard底层调用MIGRATE命令其语法为MIGRATE host port key| destination-db timeout [COPY] [REPLACE] [AUTH password] [KEYS key1 key2 ...]必须手动验证源节点能否直连目标节点的 Redis 端口非集群端口。在源节点执行redis-cli -p 6379 MIGRATE 10.0.1.100 6379 0 5000 KEYS $(redis-cli -p 6379 KEYS test:* | head -3)若返回(integer) 1说明连通且权限正常若报错IOERR检查目标节点bind和protected-mode no若报错WRONGPASS确认目标节点requirepass是否与MIGRATE命令中AUTH参数一致。2.6 检查客户端驱动是否支持ASK重定向扩容期间客户端可能收到ASK slot ip:port响应而非MOVED表示该槽位正在迁移中应临时将请求发往指定地址。老版本 Jedis 3.0、Lettuce 4.0默认不处理ASK会导致请求失败。验证方法用redis-cli -c启用集群模式连接集群执行SET testkey value观察是否自动重定向。若失败必须升级客户端驱动或在应用层实现ASK处理逻辑。2.7 确认监控与告警已覆盖关键指标迁移过程需实时关注cluster_stats_messages_sent/received突增说明心跳或迁移消息激增keyspace_hits/misses迁移中misses会短暂上升evicted_keys若非零说明内存不足触发驱逐migrate_requests目标节点该指标应持续增长。我习惯在 Grafana 中设置阈值告警migrate_requests5 分钟内无增长 → 触发人工检查evicted_keys 0 → 立即暂停迁移。没有监控的扩容如同蒙眼开车。3. 扩容实操全流程从reshard到flushall的 12 个关键动作与 5 个隐藏陷阱现在进入核心环节。以下是以 3 主 3 从6 节点集群扩容至 4 主 4 从8 节点为例的完整流程。所有命令均基于 Redis 5.0.14 实测参数值经生产环境验证。3.1 步骤一计算并分配槽位——为什么必须手动算而不是让工具“平均分”redis-cli --cluster reshard默认提供--cluster-yes自动模式但它会将槽位“平均”分配忽略各节点实际负载。例如原集群 A/B/C 三主节点内存使用率分别为 40%/75%/30%若直接平均分 4096 个槽16384÷4A 节点将新增约 1365 个槽B 节点仅增 300 个C 节点增 1365 个——这会导致 B 节点负载进一步恶化。真正的扩容必须基于负载均衡而非数字平均。正确做法用redis-cli -p 6379 --cluster check获取各节点槽位分布计算各节点当前槽位数及内存使用率设定目标新节点 D 应承接原集群中负载最高节点B的部分槽位。假设 B 节点当前持有 5461 个槽16384×1/3内存使用率 75%我们计划将其中 2048 个槽迁至 D。计算依据2048 ÷ 16384 ≈ 12.5%对应内存负载下降约 12.5% × 75% 9.4%降至 65.6%符合安全水位70%。注意槽位迁移必须以 1024 的整数倍进行Redis 内部最小迁移单元2048 是合理选择。小于 1024 可能因网络延迟导致超时失败大于 4096 则单次迁移时间过长增加风险。3.2 步骤二执行reshard命令——交互式输入的 5 个致命细节在任意节点执行redis-cli --cluster reshard 10.0.1.10:6379按提示输入How many slots do you want to move输入2048必须与步骤一计算一致What is the receiving node ID粘贴新节点 D 的 ID从CLUSTER NODES复制确保无空格Please enter all the source node IDs输入all让工具自动选择源节点或指定 B 节点 IDDo you want to proceed with the proposed reshard plan输入yes。陷阱一all选项的误导性all并非“所有节点”而是“所有主节点中槽位数最多的那个”。若你有多个高负载节点它只会选一个。必须手动输入源节点 ID如abcd1234...并确保该 ID 对应 B 节点。陷阱二ID 复制时的隐形空格从CLUSTER NODES复制 ID 时末尾常带换行符或空格。粘贴后若命令卡住大概率是 ID 格式错误。建议用echo abcd1234... | xargs清理后再输入。陷阱三迁移过程中的BUSYKEY错误若源节点正对该槽内 key 执行DEL、EXPIRE等操作MIGRATE会返回BUSYKEY。此时工具会暂停并等待但默认超时时间为 60 秒。若超时迁移中断。解决方案在迁移前用redis-cli -p 6379 --scan --pattern key:* | head -1000 | xargs -n 1 redis-cli -p 6379 DEL清理热点 key 的临时状态。3.3 步骤三监控迁移进度——如何读懂CLUSTER NODES的“沉默语言”迁移启动后不再依赖工具输出而要实时解析CLUSTER NODES。重点关注三列Slot range源节点行末显示0-5460目标节点行末显示-空→ 迁移未开始Flags源节点 flags 出现migrating目标节点出现importing→ 迁移中Connected两节点状态均为connected→ 网络正常。典型迁移中状态流B节点: abcd1234... 10.0.1.20:637916379 master - 0 1699999999000 2 connected 0-5460 migrating-2048 D节点: efgh5678... 10.0.1.100:637916379 master - 0 1699999999000 4 connected importing-2048migrating-2048表示 B 正在迁移 2048 个槽importing-2048表示 D 正在导入。当 B 的migrating消失D 的importing消失且 D 的 slot range 更新为5461-7508假设从 5461 开始则迁移完成。经验若migrating持续存在超 30 分钟立即检查redis.log中MIGRATE相关日志。常见原因是目标节点maxmemory达到上限或tcp-backlog不足导致连接拒绝。3.4 步骤四强制刷新槽位映射——为什么CLUSTER RESET是必要但危险的操作迁移完成后客户端缓存的槽位映射可能未更新仍向旧节点发送请求触发MOVED错误。此时需执行CLUSTER RESET强制刷新。但注意CLUSTER RESET有 hard/soft 两种模式必须用hard在新节点 D 上执行redis-cli -p 6379 CLUSTER RESET hardhard模式会清空nodes.conf中所有槽位信息重新从集群拉取最新配置soft模式仅重置节点 ID不更新槽位无效。执行后D 节点会短暂断开集群连接约 5 秒后自动重连并同步状态。陷阱四CLUSTER RESET的连锁反应若在迁移未完全结束时执行D 节点会丢失importing状态导致已迁移的槽位无法归属集群报CLUSTERDOWN。必须严格确认CLUSTER NODES中 D 的 slot range 已更新且 flags 无importing后再执行。3.5 步骤五添加从节点并建立主从关系——replicate命令的原子性边界为新主节点 D 添加从节点 E需在 E 节点执行redis-cli -p 6379 CLUSTER REPLICATE abcd1234... # D 的节点 ID陷阱五REPLICATE不保证即时生效执行后E 的CLUSTER NODES中 flags 会立即变为slave但master字段仍为空直到全量同步完成。此时若 D 宕机E 无法自动升级因未完成握手。必须等待INFO replication中master_link_status:up且slave_repl_offset接近master_repl_offset后再验证CLUSTER NODES中 E 的master字段已填入 D 的 ID。3.6 步骤六验证数据一致性——redis-cli --cluster check的深层解读执行redis-cli --cluster check 10.0.1.10:6379重点检查三项 Performing Cluster Check (using node 10.0.1.10:6379)确认检查节点是健康的M: abcd1234... - 1 keys每个主节点后的 key 数量应与预期槽位数匹配如 D 节点应有 ~2048 个 key[OK] All nodes agree about slots configuration.表示所有节点槽位视图一致。若出现[WARNING] Node 10.0.1.100:6379 has slots in importing or migrating state.说明迁移未完成需等待。3.7 步骤七清理残留配置——nodes.conf的手动编辑风险迁移后旧节点 B 的nodes.conf中仍保留已迁出槽位的记录。虽然不影响运行但会增大文件体积且下次CLUSTER RESET可能读取错误信息。严禁直接删除nodes.conf正确做法停止 B 节点redis-cli -p 6379 SHUTDOWN编辑nodes.conf找到 B 节点行将0-5460改为0-3412即减去 2048启动 B 节点执行CLUSTER NODES确认槽位范围已更新。提示nodes.conf是二进制格式但实际为文本。编辑时切勿改动节点 ID、IP 或 port 字段只修改 slot range。3.8 步骤八客户端灰度切换——为什么不能“一刀切”即使集群检查通过客户端仍需灰度验证。我采用三级灰度Level 11% 流量在 Nginx 层通过cookie或header将 1% 请求路由至新集群配置Level 210% 流量观察 5 分钟确认latency_ms无突增、error_rate 0.1%Level 3100% 流量全量切换持续监控 1 小时。曾有一次Level 1 测试正常Level 2 却出现大量TRYAGAIN错误——原因是新节点timeout参数为 5000ms而旧节点为 1000ms客户端重试策略未适配。灰度是唯一能暴露这种隐性问题的方式。3.9 步骤九性能压测对比——用redis-benchmark捕捉微秒级差异在扩容前后用相同参数压测redis-benchmark -h 10.0.1.10 -p 6379 -t set,get -n 100000 -q关键对比指标SET: xxx.xxx requests per second应提升 ≥ 15%因新增节点分担请求GET: xxx.xxx requests per second若下降说明新节点网络延迟高或 CPU 不足latency分布P99 延迟应 ≤ 5ms原集群 P99 为 4ms。若SET提升但GET下降检查新节点vm.swappiness是否为 0避免交换分区拖慢响应。3.10 步骤十日志归档与回滚预案——nodes.conf备份的黄金法则在执行reshard前必须备份所有节点的nodes.conffor ip in 10.0.1.{10,20,30,100,110,120}; do scp $ip:/var/lib/redis/nodes.conf nodes.conf.$ip.$(date %s) done回滚步骤若迁移失败停止所有节点将备份的nodes.conf拷贝回原路径启动节点执行CLUSTER RESET hard用redis-cli --cluster check验证。没有备份的回滚等于在生产环境裸泳。3.11 步骤十一更新监控与告警规则——新增节点的 3 个必配指标扩容后监控系统需新增redis_cluster_node_slots_assigned{nodeD}D 节点分配槽位数阈值设为 2048redis_cluster_node_replication_lag_seconds{nodeE}E 节点复制延迟阈值 5s 告警redis_cluster_migrate_requests_total{nodeD}D 节点接收迁移请求数用于分析迁移效率。漏配这些等于给新节点装了“哑巴监控”。3.12 步骤十二最终验证与交付——用SCANEXISTS确保零丢失编写脚本遍历所有槽位随机抽样验证 key 存在性# 生成槽位列表0-16383 seq 0 16383 slots.txt # 对每个槽位取 10 个随机 key 检查 while read slot; do redis-cli -c -p 6379 --cluster call 10.0.1.10:6379 SCAN 0 MATCH slot_${slot}:* COUNT 10 2/dev/null | \ awk {print $2} | head -10 | xargs -I {} redis-cli -c -p 6379 EXISTS {} done slots.txt | grep 1 | wc -l若返回数 总抽样数 × 0.99说明存在 key 丢失需立即排查MIGRATE日志。4. 缩容的生死线从“踢出节点”到“数据回收”的 8 个不可逆操作缩容比扩容风险更高因为它是“主动销毁”而非“被动增加”。一次错误的缩容可能导致 16384 个槽位中的 4096 个永久丢失。以下流程基于将 4 主 4 从缩容回 3 主 3 从移除节点 D/E。4.1 第一原则缩容必须从从节点开始且必须确保主节点无故障绝对禁止直接踢出主节点。正确顺序先踢出从节点 E等待 D 成为独立主节点无从节点将 D 的槽位迁出最后踢出 D。若跳过第 1 步直接踢出 DE 会因失去主节点而进入fail状态集群自动触发故障转移可能将其他节点提升为 D 的从节点导致拓扑混乱。4.2 步骤一安全踢出从节点 E——CLUSTER FORGET的精确时机在任意节点执行redis-cli -p 6379 CLUSTER FORGET efgh5678... # E 的节点 ID关键点FORGET命令必须在 E 节点停止运行后执行。若 E 仍在运行其他节点会持续向其发送PINGE 会回应PONG导致FORGET失效。正确流程redis-cli -p 6379 SHUTDOWN停止 E等待 30 秒确保心跳超时执行CLUSTER FORGET。执行后所有节点CLUSTER NODES中 E 的条目消失。4.3 步骤二迁移 D 的槽位——为什么必须指定源节点为 D而非all缩容时reshard的源节点必须明确指定为 D。若用all工具可能选择其他节点迁移导致 D 的槽位未被清空后续无法踢出。命令redis-cli --cluster reshard 10.0.1.10:6379 # How many slots: 2048 # Receiving node: abcd1234... # B 节点 ID # Source node: efgh5678... # D 节点 ID不是 all4.4 步骤三等待迁移完成并验证——CLUSTER NODES中 D 的槽位必须为0迁移完成后D 的CLUSTER NODES行应显示efgh5678... 10.0.1.100:637916379 master - 0 1699999999000 4 connected末尾无 slot range表示无槽位。若仍有5461-7508说明迁移未完成不可进行下一步。4.5 步骤四踢出主节点 D——CLUSTER FORGET的双重保险同样先停止 Dredis-cli -p 6379 SHUTDOWN然后在所有剩余主节点A/B/C上执行CLUSTER FORGET# 在 A 节点执行 redis-cli -p 6379 CLUSTER FORGET efgh5678... # 在 B 节点执行 redis-cli -p 6379 CLUSTER FORGET efgh5678... # 在 C 节点执行 redis-cli -p 6379 CLUSTER FORGET efgh5678...为什么不是只在一个节点执行CLUSTER FORGET是局部命令只影响当前节点。若只在 A 执行B/C 仍会向 D 发送心跳D 重启后会重新加入集群。必须全覆盖。4.6 步骤五清理nodes.conf中的残留信息——手动删除的 3 行D 停止后其nodes.conf仍存在。需手动编辑删除包含 D 节点 ID 的整行以及该行后的vars段落通常 2-3 行。删除后D 的nodes.conf可安全删除。4.7 步骤六强制重载集群配置——CLUSTER RESET的必要性在所有剩余节点执行redis-cli -p 6379 CLUSTER RESET hard确保CLUSTER NODES中 D 的条目彻底消失且槽位总数仍为 16384。4.8 步骤七最终验证与数据回收——redis-cli --cluster check的终极考验执行redis-cli --cluster check 10.0.1.10:6379必须满足All 16384 slots covered.M: abcd1234... - 5461 keysB 节点 key 数应增加约 2048S: ... - 0 keys所有从节点 key 数为 0表示无数据残留。若S:行有 key 数说明从节点未完全同步需手动FLUSHALL。5. 那些文档里不会写的实战经验从 12 次扩缩容中提炼的 7 条血泪教训这些不是理论而是我在不同业务场景支付、社交、IoT中用服务器宕机、用户投诉、深夜加班换来的认知。它们无法被自动化只能靠人来判断。5.1 “迁移速度”不是越快越好而是要与业务低峰期严格对齐我曾为赶进度在早 9 点业务高峰启动迁移结果MIGRATE占用大量网络带宽导致支付接口平均延迟从 12ms 升至 89ms触发熔断。后来我们制定铁律所有迁移必须在业务低峰期如凌晨 2-4 点执行且单次迁移槽位 ≤ 1024。1024 是经过压测的平衡点小于它迁移太慢大于它网络抖动概率陡增。用--cluster-replica参数控制迁移并发数默认 1设为 2 可提速但必须同步监控net_io。5.2CLUSTER NODES的ping-sent和pong-recv时间差是定位网络抖动的黄金指标这两字段分别表示该节点上次发送PING和收到PONG的毫秒时间戳。正常差值 100ms。若某节点差值持续 500ms说明其与该节点间网络延迟高。我曾用此发现某交换机端口 CRC 错误率超标更换后迁移成功率从 63% 提升至 100%。5.3 客户端连接池的maxTotal必须 ≥ 2 × 节点数 × 每节点连接数JedisPool 默认maxTotal8。在 8 节点集群中若每个节点需 4 个连接则至少需 32 个连接。否则连接池耗尽会触发JedisConnectionException表现为大量Cannot get Jedis connection错误。计算公式maxTotal 节点数 × 每节点连接数 × 1.5冗余。5.4redis.conf中tcp-keepalive 300是救命参数Linux 默认tcp_keepalive_time72002 小时。若客户端与 Redis 间存在 NAT 设备空闲连接 5 分钟即被断开但 Redis 不知情仍向该连接写入数据导致Broken pipe。设为 3005 分钟可及时探测并关闭失效连接。5.5 迁移前执行CLIENT PAUSE 5000比停业务更优雅CLIENT PAUSE会阻塞所有客户端请求 5 秒但不关闭连接。相比停业务它避免了连接风暴大量客户端重连。在迁移开始前执行可确保无新请求进入