Redis Cluster 的扩容和缩容是每个用 Redis 做核心缓存的团队迟早要面对的事。刚开始数据量小三个主节点扛着挺舒服业务一涨、QPS 一飙、内存告警一响就得加机器反过来业务低谷或者机器成本要压缩又得把节点撤下来。听起来就是加加减减但真上手操作过的都知道槽位迁移、数据重平衡、客户端路由刷新这几步里随便哪个环节没处理好都可能出现请求超时、数据倾斜甚至集群状态异常。这篇内容就是把我自己在生产环境里反复做过多次的扩容缩容流程完整拆开从原理到命令、从参数计算到踩坑记录全部讲清楚。不管你是刚接触 Redis Cluster 的运维新手还是已经管着几十个节点但没亲手做过扩缩容的开发者都能照着这套流程直接落地。1. 先搞清楚 Redis Cluster 的数据分布逻辑1.1 哈希槽才是集群的基本单位很多人一上来就急着敲命令加节点结果加完发现数据没过去新节点空着老节点还是满的。根本原因就是没理解 Redis Cluster 的数据分布机制。它不像一致性哈希那样把数据直接映射到节点上而是引入了一层叫**哈希槽Hash Slot**的中间结构。整个集群固定有16384 个槽每个键通过CRC16(key) mod 16384算出一个槽号然后这个槽归属于某个主节点。所以真正决定数据在哪儿的不是节点本身而是槽的分配关系。这意味着扩容的本质不是把数据搬到新节点而是把一部分槽的归属权转移给新节点。槽一旦迁走这个槽上所有的键自然就跟着走了。缩容也是同理你得先把要下线的节点上的槽全部转出去一个不留才能安全移除它。理解这一点后面所有操作你都能想明白为什么这么做。那为什么是 16384 而不是 65536这是作者在权衡心跳包大小和集群规模后定的值。集群节点间的心跳包会携带槽位 bitmap16384 个槽用 2KB 的 bitmap 就能表示65536 则要 8KB。对于动辄几十上百节点的集群心跳包太大会明显增加网络开销。而且实际生产中集群主节点很少超过 1000 个16384 个槽完全够分。这个数字你改不了也不用改记住就行。1.2 槽迁移过程中集群仍然可用这是 Redis Cluster 设计里最值得称赞的一点迁移槽的时候集群不需要停机。当你要把某个槽从节点 A 迁到节点 B这个槽会经历一个中间状态——一部分键在 A一部分键在 B。此时客户端如果请求的键还在 A 上A 正常返回如果键已经迁到 BA 会返回一个ASK重定向告诉客户端这个键现在在 B你去 B 拿但这次别更新本地路由表。客户端拿到 ASK 后临时去 B 请求一次下次同样的键还是先问 A。等槽里所有键都迁完再用CLUSTER SETSLOT把槽正式指派给 B这时候才会返回MOVED重定向客户端才会永久更新路由缓存。这个 ASK 和 MOVED 的区别非常关键ASK 是临时跳转MOVED 是永久改派。理解了这套机制你就明白为什么迁移过程中业务基本无感——只要客户端支持 Cluster 协议重定向是自动处理的。注意如果你的客户端用的是不支持 ASK/MOVED 重定向的简易实现迁移期间会大量报错。生产环境务必确认客户端是标准的 Cluster 客户端比如 Jedis、Lettuce、redis-py-cluster 这类。1.3 扩容缩容到底在解决什么问题把场景说具体一点。假设你有一个 3 主 3 从的集群每个主节点平均扛 5461 个槽。业务增长后某个主节点内存到了 80%QPS 也接近单线程处理上限。这时候你有两个选择垂直扩容给这台机器加内存、换更强的 CPU或者水平扩容加新节点分担槽。垂直扩容有天花板而且单节点内存越大做 RDB 持久化和主从全量同步时 fork 的耗时越长阻塞风险越高。所以主流做法是水平扩容。缩容则常见于几种情况业务迁移后某个集群流量骤降、测试环境要回收机器、或者当初扩容扩多了要缩回来省成本。缩容比扩容更危险因为你要确保下线节点上的槽一个不剩而且如果有从节点跟着下线还要考虑故障转移的副本数够不够。这些细节后面会逐一展开。2. 扩容前的准备工作与容量估算2.1 扩容前必须确认的集群健康状态动手加节点之前有几项检查是必须做的跳过任何一项都可能让扩容变成事故。第一件事是确认集群状态是ok而不是failredis-cli --cluster check 10.0.0.1:7000这条命令会输出集群的槽覆盖情况、每个节点的键数量、配置一致性等。重点看两个地方一是所有 16384 个槽是否都有归属二是各节点的cluster_state是否为ok。如果有槽处于开放状态open slot或者有节点被标记为 fail必须先修复再扩容否则迁移过程中会不断报错。第二件事是确认没有正在进行的迁移任务。用CLUSTER NODES看每个节点的状态如果某个槽后面跟着[slot--node]或[slot--node]这种标记说明有迁移没完成。这时候贸然加节点会干扰正在进行的迁移导致槽状态混乱。第三件事是检查各节点内存使用率。用INFO memory看used_memory和maxmemory的比例。如果某个节点已经超过 75%扩容过程中它还要往外迁数据迁移本身会占用额外内存迁移缓冲区有触发淘汰甚至 OOM 的风险。稳妥起见先把内存压到 70% 以下再操作。2.2 新节点该配多大、加几个新节点的规格选择有个经验公式新节点内存 ≥ 现有单节点平均内存 × 1.2。留 20% 余量是为了应对迁移后的数据增长和迁移过程中的临时开销。如果你现有节点平均用了 20GB新节点至少配 24GB 的可用内存。CPU 方面Redis 是单线程处理命令的所以单核性能比核数更重要但集群模式下每个节点至少给 2 核一个核处理主线程另一个应对持久化和网络中断处理。加几个节点取决于你的目标。如果只是想让每个节点内存降下来可以按当前总内存除以目标单节点内存来算。比如总共 60GB 数据分布在 3 个节点你想让每个节点降到 15GB 左右那就需要 4 个节点也就是加 1 个。但实际中我更建议一次加 2 个或 3 个原因是加 1 个节点后槽的重新分配往往不够均匀而且如果后续还要再扩频繁操作增加风险。一次加到位让槽分布更均衡。还有个容易被忽略的点新节点最好和现有节点在不同物理机上。如果为了省事把新节点和某个老节点放同一台机器那这台机器一挂集群同时损失两个节点故障转移的压力会陡增。生产环境务必做物理隔离。2.3 从节点要不要一起加扩容主节点的同时强烈建议给新主节点配上对应的从节点。Redis Cluster 默认的故障转移机制要求主节点有至少一个从节点否则主挂了这个主负责的槽就没人接管集群会进入 fail 状态。如果你只加主节点不加从节点等于给集群埋了个雷。从节点的配置和主节点可以一样也可以稍小一点因为从节点平时只做数据同步不处理请求除非开启读写分离。但内存不能太小否则全量同步时从节点缓冲区不够会导致同步失败。一般从节点内存配主节点的 80% 以上比较稳妥。加从节点的命令后面会讲它和加主节点的流程略有不同。3. 扩容实操从加节点到槽迁移完成3.1 启动新节点并加入集群假设现有集群是 3 主 3 从节点在 7000-7005 端口现在要加一组新的主从主节点 7006、从节点 7007。首先在新机器上把 Redis 配置文件准备好关键配置项如下port 7006 cluster-enabled yes cluster-config-file nodes-7006.conf cluster-node-timeout 15000 appendonly yes maxmemory 24gb maxmemory-policy noeviction这里maxmemory-policy设成noeviction是有讲究的。集群模式下如果设成allkeys-lru之类的淘汰策略当内存满了会静默淘汰键而集群的槽迁移依赖键的完整性淘汰可能导致迁移过程中键丢失。所以集群节点一般用noeviction内存不够就报错让你主动处理而不是悄悄丢数据。启动新节点后用redis-cli --cluster add-node把它加入集群redis-cli --cluster add-node 10.0.0.6:7006 10.0.0.1:7000第一个参数是新节点地址第二个是集群中任意一个已知节点。执行后新节点会以主节点身份加入但此时它没有任何槽所以不参与数据服务。你可以用CLUSTER NODES确认它已经出现在节点列表里。接着加从节点注意加从节点时要指定它跟随哪个主节点redis-cli --cluster add-node 10.0.0.7:7007 10.0.0.1:7000 --cluster-slave --cluster-master-id 7006的节点ID7006的节点ID从CLUSTER NODES输出里拿是一串 40 位的十六进制字符串。加完后用CLUSTER NODES确认 7007 的 role 是 slave 且 master 指向 7006。提示加从节点时如果忘了指定 master-id它会随机跟随一个主节点可能导致某个主节点副本过多而新主节点没有副本。加完一定要核对。3.2 用 reshard 命令迁移槽节点加好了接下来是核心步骤——把一部分槽从老节点迁到新节点。Redis 提供了--cluster reshard命令来自动化这个过程redis-cli --cluster reshard 10.0.0.1:7000执行后它会交互式地问你几个问题。第一个是要迁移多少个槽这里需要你算一下。假设原来 3 个主节点各 5461 个槽现在变成 4 个主节点理想情况是每个 4096 个槽。所以从每个老节点各迁出 1365 个槽给新节点总共迁 4096 个。你可以直接输入 4096。第二个问题是接收这些槽的节点 ID 是什么输入 7006 的节点 ID。第三个问题是从哪些节点迁出这里可以输入all让所有老节点平均分摊也可以指定具体节点。输入all后它会自动从每个有槽的节点均匀抽取。确认后迁移就开始了你会看到类似这样的输出Moving slot 0 from 10.0.0.1:7000 to 10.0.0.6:7006 Moving slot 1 from 10.0.0.1:7000 to 10.0.0.6:7006 ...迁移速度取决于数据量和网络带宽。如果槽里有大 key单个槽的迁移可能耗时较长。整个过程是逐个槽串行迁移的所以槽数量越多、数据越大总时间越长。我实测过一个 20GB 数据的集群迁移 4096 个槽大概花了 8 分钟期间业务 QPS 没有明显波动。3.3 迁移过程中的关键参数调优reshard命令背后其实是一系列MIGRATE操作每个槽的迁移会分批把键从源节点传到目标节点。这里有几个参数会影响迁移效率和业务影响cluster-migration-barrier这个参数控制一个主节点至少要有多少个从节点才允许它的槽被迁移走。默认是 1意思是主节点至少保留 1 个从节点。如果你在缩容时想迁走某个主节点的所有槽而这个主节点只有 1 个从节点迁移到最后一个槽时可能会被这个参数拦住。缩容场景下需要临时调整。migrate 的 pipeline 数量MIGRATE命令有个PIPELINE选项可以一次传多个键减少往返次数。reshard默认会批量处理但如果你的键普遍较大可以适当调小批量避免单次传输阻塞太久。cluster-node-timeout迁移期间如果某个节点因为负载高导致心跳超时会被标记为疑似下线。默认 15 秒如果迁移数据量大、节点压力大可以临时调到 30 秒避免误判。但调太大也有风险真故障时发现得晚。我的经验是迁移期间保持默认如果确实出现误判再临时调整。注意迁移期间不要对集群做其他管理操作比如同时加节点、删节点、手动改槽。多个操作并发会让槽状态变得难以追踪。3.4 迁移完成后的验证迁移结束后第一件事是确认槽分布是否符合预期redis-cli --cluster check 10.0.0.1:7000输出里会列出每个节点负责的槽数量和键数量。理想情况下新节点应该有 4096 个槽老节点各 4096 个。如果发现某个节点槽数偏差较大可以用--cluster rebalance做一次再平衡redis-cli --cluster rebalance 10.0.0.1:7000 --cluster-use-empty-masters--cluster-use-empty-masters表示允许把槽分给当前没有槽的节点。rebalance 会计算各节点的槽数差异自动从多的往少的迁直到达到平衡阈值默认 2% 差异。这个命令比手动 reshard 更省心适合槽分布已经大致合理、只想微调的场景。第二件事是验证数据完整性。随机抽几个业务 key用CLUSTER KEYSLOT算出槽号再用CLUSTER COUNTKEYSINSLOT确认键在预期的节点上。更彻底的做法是对比迁移前后各节点的DBSIZE总和理论上应该一致除非迁移期间有写入。如果发现总数对不上要立即排查是否有键在迁移中丢失。4. 缩容实操安全下线节点的完整流程4.1 缩容比扩容危险在哪里缩容的核心风险在于你要把一个节点彻底从集群里拿掉而它上面可能还有槽、有数据、有从节点。如果直接del-node删节点Redis 会拒绝因为节点上还有槽。你必须先把槽全部迁走。但迁槽的过程中如果这个节点是某些槽的唯一持有者迁走之后这些槽就归别人了一旦目标节点出问题数据就没了。所以缩容前要确认目标节点有足够的容量接收这些槽。另一个风险是副本数。假设你要下线的主节点有一个从节点你把主节点的槽迁走后删掉主节点那个从节点就变成了孤儿节点没有 master。你需要把它重新指派给别的主节点或者一起删掉。如果忘了处理集群里会留一个游离的从节点虽然不影响服务但浪费资源且让集群状态看起来不干净。4.2 把待下线节点的槽全部迁出假设要下线 7006 这个主节点它现在有 4096 个槽。用 reshard 把这些槽迁到其他节点redis-cli --cluster reshard 10.0.0.1:7000 \ --cluster-from 7006的节点ID \ --cluster-to 目标节点ID \ --cluster-slots 4096 \ --cluster-yes这里用了非交互模式--cluster-yes表示自动确认。--cluster-from指定源节点--cluster-to指定目标节点--cluster-slots指定迁多少槽。注意目标节点可以是多个但一条命令只能指定一个 to所以要迁到多个节点就执行多次每次迁一部分。迁完后用CLUSTER NODES确认 7006 后面不再有任何槽的标记。如果还有残留重复 reshard 直到清空。这里有个细节如果 7006 是某些槽的唯一持有者迁走最后一个槽时可能会因为cluster-migration-barrier而失败。这时候需要临时把 barrier 设为 0redis-cli -h 10.0.0.6 -p 7006 config set cluster-migration-barrier 0迁完再改回来。这个参数是节点级别的改一个节点不影响其他节点。4.3 删除节点与从节点处理槽清空后就可以删节点了redis-cli --cluster del-node 10.0.0.1:7000 7006的节点ID删完后 7006 进程还在跑但已经不属于集群了。你可以停掉它的进程回收机器。如果 7006 有从节点 7007删掉主节点后 7007 会变成没有 master 的状态。处理方式有两种一是把 7007 重新指派给另一个主节点用CLUSTER REPLICATE 新master-id二是如果不需要这个副本直接del-node删掉 7007。重新指派从节点的命令要在从节点上执行redis-cli -h 10.0.0.7 -p 7007 cluster replicate 新master的节点ID执行后 7007 会开始从新主节点同步数据。如果新主节点数据量大全量同步会花一些时间期间 7007 状态是sync。同步完成后变成connected才算真正可用。提示缩容时如果一次下线多个节点建议一个一个来每下线一个就做一次cluster check确认集群状态正常再操作下一个。批量操作一旦出问题排查起来非常麻烦。4.4 缩容后的槽均衡检查所有要下线的节点处理完后集群的槽分布可能变得不均匀。比如你从 4 个节点缩到 3 个槽可能集中在某几个节点上。这时候跑一次 rebalanceredis-cli --cluster rebalance 10.0.0.1:7000rebalance 会自动把槽从多的节点迁到少的节点让分布重新均衡。注意缩容后的 rebalance 不要加--cluster-use-empty-masters因为此时没有空主节点了加了也没意义。rebalance 完成后再次cluster check确认所有节点槽数接近、集群状态 ok缩容才算真正结束。5. 扩缩容常见问题与排查速查5.1 迁移卡住或超时怎么办迁移过程中最常见的现象是某个槽卡住不动日志里反复出现MIGRATE相关错误。原因通常有几个一是源节点或目标节点负载过高导致命令处理慢二是网络抖动MIGRATE 的 TCP 连接超时三是槽里有超大 key单个 key 传输耗时过长。排查时先看源节点和目标节点的INFO stats关注instantaneous_ops_per_sec和blocked_clients。如果 ops 很高说明节点忙不过来可以暂停迁移等负载降下来再继续。如果是网络问题检查节点间的连通性和延迟。如果是大 key用redis-cli --bigkeys找出大 key评估是否需要在业务低峰期迁移。如果迁移真的卡死了可以用CLUSTER SETSLOT slot STABLE把槽状态重置然后重新发起迁移。但这一步要谨慎确保没有客户端正在依赖这个槽的中间状态。5.2 客户端报 MOVED 或 ASK 错误正常情况下客户端应该自动处理 MOVED 和 ASK如果业务代码里看到这两种错误说明客户端配置有问题。检查客户端的集群模式是否开启比如 Jedis 要用JedisCluster而不是JedisLettuce 要用RedisClusterClient。另外确认客户端连接的是集群节点而不是单机节点单机客户端连到集群节点上会收到 MOVED 但无法自动跳转。还有一种情况是客户端路由缓存过期。迁移完成后客户端可能还缓存着旧的槽映射导致请求发到错误的节点。标准客户端会在收到 MOVED 后更新缓存但如果客户端实现有 bug 或者缓存更新逻辑有问题可能需要重启客户端。生产环境建议用成熟的客户端库不要自己实现 Cluster 协议。5.3 迁移后数据对不上这是最让人紧张的问题。迁移后如果发现某个 key 查不到先别慌按这个顺序排查第一用CLUSTER KEYSLOT key算出槽号再用CLUSTER COUNTKEYSINSLOT slot在源节点和目标节点分别查确认键到底在哪儿。第二检查是否有键在迁移过程中因为过期被删除了如果 key 设了 TTL 且迁移耗时较长可能在传输前就过期了。第三检查是否有客户端在迁移期间写了数据到源节点而源节点迁移完这个槽后不再接收该槽的写入导致写入丢失。第三种情况最隐蔽。迁移一个槽的过程中如果客户端往这个槽写数据源节点会正常接收因为槽还没正式移交但这些新写入的键不会被迁移迁移只处理迁移开始时刻存在的键。等槽正式移交后这些键就留在了源节点成为孤儿键。所以迁移期间最好避免对正在迁移的槽做写入或者迁移完成后做一次全量数据校验。5.4 常见问题速查表问题现象可能原因排查方法解决方式迁移卡住不动节点负载高/网络抖动/大key看 INFO stats 的 ops 和延迟降负载、检查网络、低峰期迁移客户端报 MOVED客户端非集群模式检查客户端类型换成 Cluster 客户端客户端报 ASK迁移中正常现象确认客户端支持 ASK标准客户端自动处理迁移后 key 丢失TTL 过期/迁移期间写入CLUSTER COUNTKEYSINSLOT 定位迁移期间避免写入迁移后校验槽迁移被拒绝migration-barrier 限制看节点从节点数量临时设 barrier 为 0集群状态 fail有槽未分配/节点失联cluster check 看槽覆盖修复失联节点或重新分配槽新节点没数据槽没迁过去CLUSTER NODES 看槽归属执行 reshard 迁移槽5.5 几个我踩过的坑第一个坑是迁移期间做持久化。有一次我在迁移过程中触发了 RDB 保存fork 出来的子进程和迁移操作抢 CPU导致迁移速度骤降业务延迟飙升。后来我养成的习惯是迁移前手动BGSAVE一次然后临时关闭自动保存CONFIG SET save 迁移完成后再恢复。这样迁移期间不会有持久化干扰。第二个坑是从节点同步拖慢迁移。新加的主节点如果有从节点从节点会做全量同步这个同步和槽迁移是并行的两者都吃网络带宽。如果带宽不够迁移会非常慢。解决办法是先让从节点同步完成再开始迁槽。加从节点后等它的状态变成connected再执行 reshard。第三个坑是忘了更新监控。扩容后集群节点数变了但监控系统里还是老的节点列表导致新节点的指标没采集到出了问题看不到。每次扩缩容后记得同步更新监控配置、告警规则和客户端的节点列表。6. 扩缩容后的收尾与长期维护建议6.1 收尾检查清单扩缩容操作完成后别急着收工按这个清单过一遍集群状态是否 ok、所有槽是否都有归属、各节点槽数是否均衡、数据总量是否与操作前一致、客户端是否正常无报错、监控是否覆盖新节点、告警规则是否更新。这七项都确认了才算真正完成。数据总量校验有个简单方法在操作前后分别对所有主节点执行DBSIZE并求和对比两个总数。如果差异在合理范围内比如迁移期间有正常写入说明数据没丢。如果差异很大就要深入排查。6.2 什么时候该扩容什么时候该优化不是所有性能问题都要靠扩容解决。在决定加节点之前先看看有没有优化空间有没有大 key 拖慢单节点、有没有热 key 导致某个槽压力集中、有没有不必要的全量扫描命令。我见过一个案例某个集群 QPS 上不去排查发现是业务在用一个KEYS *命令直接打满了单节点 CPU。把这条命令改成SCAN后QPS 立刻降了一半根本不用扩容。所以扩容前先做一轮性能分析用SLOWLOG看慢查询、用--bigkeys找大 key、用--hotkeys找热 key。把这些优化掉之后如果还是不够再考虑加节点。这样能避免花冤枉钱。6.3 建立扩缩容的标准操作流程扩缩容这种事最好沉淀成标准流程文档每次操作照着走。文档里要包含操作前的检查项、具体的命令序列、每一步的预期结果、异常情况的处理方式、操作后的验证项。这样即使换人操作也不会因为不熟悉而出错。我个人习惯在流程里加一个回滚方案。扩容的回滚相对简单把新加的节点删掉、槽迁回原节点即可。缩容的回滚就麻烦了节点都下线了要重新加回来再迁槽。所以缩容前一定要确认清楚最好在业务低峰期做并且提前通知相关方。6.4 关于槽数量和节点数量的长期规划最后聊一个长期问题集群规模怎么规划。Redis Cluster 的槽数是固定的 16384节点越多每个节点分到的槽越少。当节点数超过 1000 时平均每个节点只有 16 个槽槽的粒度就太粗了容易出现数据倾斜。所以单集群主节点数量建议控制在几百以内。如果业务规模真的很大更好的做法是拆成多个集群按业务维度分片而不是无限扩一个集群。另外节点数量最好是 3 的倍数这样槽能分得更均匀。3 主、6 主、9 主都是常见配置。如果你现在是 4 主想扩到 5 主槽分布会有点别扭不如直接扩到 6 主。这个细节在规划阶段想清楚能省掉后面很多调整的麻烦。我在实际运维中的体会是Redis Cluster 的扩缩容本身不复杂命令就那么几条难的是对细节的把控和对异常的预判。每次操作前把检查项过一遍操作中盯紧集群状态操作后做好验证基本不会出大问题。真正出问题的往往不是命令敲错了而是忽略了某个前置条件或者漏掉了某个收尾步骤。把流程固化下来比记住多少命令都管用。