Redis集群采用虚拟槽分区机制,将16384个哈希槽均匀分配到多个主节点上。每个节点负责一部分槽位,客户端通过CRC16(key)%16384计算槽号并路由到对应节点。当业务数据量增长超过单节点承载能力,或者需要下线部分机器时,必须在保证数据一致性和可用性的前提下调整集群拓扑。扩容就是增加新节点并迁移部分槽位,缩容则是先迁走槽位再移除节点。整个过程涉及槽位规划、数据迁移和客户端感知,操作不当可能造成数据丢失或服务中断。

一、扩容缩容前的关键概念与检查项
执行任何集群变更之前,必须先确认集群当前状态是健康的。使用redis-cli -p 7000 cluster info查看集群状态,重点看cluster_state是否为ok,以及cluster_slots_assigned是否等于16384。如果槽位没有全部分配,或者存在fail状态的节点,需要先修复再操作。同时要确认所有节点使用的Redis版本一致,尤其是集群模式下的协议兼容性,版本差异过大会导致节点无法正常通信。
另一个关键点是理解节点ID和集群总线端口。每个Redis集群节点除了对外服务端口外,还会使用服务端口加10000的集群总线端口进行节点间通信,例如服务端口7000对应的总线端口是17000。防火墙或安全组必须同时放行这两个端口,否则新节点加入后会出现握手失败。此外,目标新节点必须是干净的、没有数据且没有配置cluster-enabled yes之外的旧集群信息。如果之前加入过其他集群,需要清空数据目录和配置中的cluster-config-file,避免残留的节点ID冲突。
在扩容前还要规划槽位迁移比例。假设原集群有3个主节点,每个节点大约承载5461个槽位,新增一个主节点后理想情况是每个节点约4096个槽位。这个比例决定迁移的数据量和耗时,也影响迁移过程中的网络带宽和内存压力。建议在业务低峰期操作,并为每次迁移设置合理的超时时间。
二、节点扩容操作:添加主节点与从节点
Redis官方提供了redis-cli --cluster命令来简化集群管理。添加主节点的命令格式如下:
# 将新节点 127.0.0.1:7006 加入已有集群 redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000
这条命令中,127.0.0.1:7006是待加入的新节点地址,127.0.0.1:7000是集群中任意一个现有节点地址。执行后,新节点会被分配一个随机节点ID,并以空槽位的主节点身份加入集群。此时使用redis-cli -p 7000 cluster nodes可以看到新节点已经出现在节点列表中,但它的connected状态可能带有no slots标记,表示还没有负责任何槽位。
如果希望新节点作为从节点加入,需要指定--cluster-slave参数,并可选通过--cluster-master-id指定要跟随的主节点ID。例如:
# 将 127.0.0.1:7007 作为从节点加入,并跟随节点ID为 abc123 的主节点 redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7000 --cluster-slave --cluster-master-id abc123
从节点加入后会自动同步主节点的数据,不需要手动迁移槽位。如果省略--cluster-master-id,系统会随机选择一个主节点作为复制目标。对于需要提高读性能或增加容灾能力的场景,建议为每个主节点配置至少一个从节点。
主节点加入后,需要为其分配槽位并迁移数据。使用redis-cli --cluster reshard命令,交互式输入迁移槽位数、目标节点ID和源节点ID。也可以使用非交互模式批量执行:
# 从所有现有主节点迁移共4096个槽位到新节点 redis-cli --cluster reshard 127.0.0.1:7000 --cluster-from all --cluster-to <target-node-id> --cluster-slots 4096 --cluster-yes
其中<target-node-id>要替换为新节点的真实节点ID。迁移过程会逐个槽位进行,每个槽位的所有键会通过MIGRATE命令原子地转移到目标节点。为了保证集群可用性,Redis采用在线迁移方式,源节点在迁移期间仍然对外提供服务,只有在单个槽位完成迁移的短暂瞬间会阻塞该槽位的请求。因此迁移过程对业务的影响是局部且可控的。
迁移完成后,建议执行redis-cli --cluster check 127.0.0.1:7000检查集群健康状态,确认所有槽位都已分配到节点且每个节点负责的槽位范围符合预期。同时可以查看cluster nodes的输出,确认新节点已经拥有对应数量的槽位。
三、节点缩容操作:槽位迁出与节点移除
缩容节点比扩容更危险,因为如果直接删除一个仍持有槽位的主节点,会导致这些槽位的数据不可用,甚至触发集群进入fail状态。正确的顺序是先将待下线节点的所有槽位迁移到其他节点,确认槽位全部迁出后,再使用del-node命令移除节点。
假设要下线的节点ID为old-node-id,可以先使用redis-cli --cluster reshard把它的槽位迁移到另一个主节点。交互模式中指定源节点为待下线节点,目标节点为剩余的主节点,迁移槽位数量等于该节点当前持有的槽位数。也可以使用非交互模式:
# 将 old-node-id 的所有槽位迁移到 new-node-id redis-cli --cluster reshard 127.0.0.1:7000 --cluster-from <old-node-id> --cluster-to <new-node-id> --cluster-slots 5461 --cluster-yes
迁移完成后,通过cluster nodes确认待下线节点已经没有槽位,其标识中不再包含槽位范围信息。此时执行del-node命令移除节点:
# 从集群中删除节点 redis-cli --cluster del-node 127.0.0.1:7000 <old-node-id>
如果待下线节点有从节点,需要先删除从节点,再删除主节点。从节点没有槽位,可以直接使用del-node删除。删除后集群会自动更新配置,其他节点会感知到拓扑变化。
缩容过程中一个常见误区是忽略客户端连接会缓存槽位映射。即使槽位已经迁走,部分客户端仍然会尝试向旧节点发送请求。虽然Redis集群会返回MOVED重定向响应,但旧节点被删除后,客户端会收到连接错误。因此建议在缩容前提前通知业务方,或者在客户端层面主动刷新集群拓扑。对于使用Proxy架构的客户端,如Codis或Twemproxy,需要确保代理层支持动态更新节点列表。
四、常见错误与线上操作最佳实践
一个非常典型的错误是直接使用普通Redis实例的方式启动新节点并执行cluster meet,然后手动分配槽位。虽然cluster meet也能让节点加入集群,但后续的槽位迁移和节点管理容易遗漏配置持久化,导致重启后集群失联。官方推荐的redis-cli --cluster命令会自动处理配置同步和节点握手,是最稳妥的方式。
另一个常见问题是迁移过程中没有设置--cluster-timeout,遇到大量数据或慢网络时迁移失败。可以在命令中添加--cluster-timeout 5000来指定每次槽位迁移的超时时间,单位是毫秒。迁移大量数据时,建议分批进行,例如每次迁移500个槽位,观察集群延迟和内存使用后再继续。这样即使出现问题,影响范围也有限,回退起来更容易。
线上操作的最佳实践可以总结为:第一,变更前完整备份RDB文件和集群配置;第二,始终在集群健康状态下操作,避免在已有节点故障时进行扩容缩容;第三,使用--cluster-yes前先在测试环境演练一遍;第四,迁移过程中持续监控cluster info中的cluster_known_nodes和cluster_slots_ok等指标;第五,缩容完成后保留一段时间空节点不要立即下线,观察客户端是否还有陈旧路由请求。这样做可以把Redis集群拓扑调整的风险降到最低。