Redis cluster扩缩容
Redis Cluster 扩缩容
Redis Cluster 通过重新分配哈希槽来扩缩容:扩容时加入空主节点,再把部分槽及槽内数据迁入;缩容时把待删除主节点的槽及数据迁出,清空后再移除节点。
本文以开源 Redis Cluster 为例。主从复制、Sentinel 部署没有这里的槽位分片机制。下面的底层原理采用传统的 MIGRATING / IMPORTING + MIGRATE 流程;新版原子槽迁移见第 7 节。
1. 数据如何分布在节点上
Redis Cluster 固定有 16384 个哈希槽,编号为 0~16383。客户端通过两层映射定位数据:
1 | |
普通 Key 的槽位计算方式为:
1 | |
Key 含有有效的 Hash Tag 时,Redis 只对 Tag 内容计算 CRC16:找到第一个 { 和它后面的第一个 },两者之间非空时使用中间内容,否则对整个 Key 计算。例如 user:{1001}:profile 和 user:{1001}:orders 属于同一个槽,便于执行同槽多 Key 操作。槽位计算和 Hash Tag 规则见 Redis Cluster 规范。
主节点负责一组槽,副本节点复制其主节点的数据。稳定状态下,每个槽只有一个主节点负责,扩缩容期间槽总数保持 16384。
| 调整方式 | 槽位处理 | 数据处理 |
|---|---|---|
| 新增主节点 | 从现有主节点迁入部分槽 | 迁入这些槽中的 Key |
| 删除主节点 | 把该节点的所有槽迁给其他主节点 | 迁出这些槽中的 Key |
| 新增副本节点 | 不改变槽归属 | 通过主从复制同步数据 |
| 删除副本节点 | 不改变槽归属 | 不需要重新分片,但会减少副本保护 |
节点加入集群后,需要运维人员或管理工具发起槽位迁移。 add-node 只完成节点加入,不会给新主节点分配槽。官方扩缩容文档
2. 扩容流程:3 个主节点扩为 4 个
假设现有主节点为 A、B、C,新主节点为 D。按相同权重分配,目标槽数如下:
| 主节点 | 扩容前槽数 | 扩容后目标槽数 |
|---|---|---|
| A | 5461 | 4096 |
| B | 5461 | 4096 |
| C | 5462 | 4096 |
| D | 0 | 4096 |
| 合计 | 16384 | 16384 |
这张表描述目标分配;具体迁哪些槽、各源节点迁多少,由迁移计划决定。节点可以持有多个不连续的槽区间。
2.1 检查集群并准备新节点
以下命令使用示例地址。NODE_A、NODE_B、NODE_C、NODE_D 等字符串需要替换为 CLUSTER NODES 中的真实节点 ID;命令中使用完整 ID。
1 | |
新节点需要启用 cluster-enabled yes,使用独立的数据目录和集群配置文件,且没有旧数据或旧集群配置。节点间应能访问客户端端口和集群总线端口;客户端还需要能够连接新节点的公布地址。
若启用了 ACL、密码或 TLS,应补齐对应连接参数,并核对节点间迁移所需的认证配置。下面省略认证参数。
2.2 加入空主节点
1 | |
第一个地址是新节点,第二个地址是现有集群的入口节点。redis-cli 检查节点状态并通过 CLUSTER MEET 建立联系,节点随后通过集群通信交换拓扑信息。D 此时没有槽,也不承载分片数据。
2.3 迁入槽位和数据
可以使用交互式重新分片:
1 | |
依次指定迁移槽数 4096、目标节点 D 的 ID、源节点 all,检查计划后确认。all 表示从其他主节点抽取槽,具体分配可能有取整差异。
指定参数的等价示例:
1 | |
如果希望管理工具按权重平衡整个集群,可以选择 rebalance。它与上面的 reshard 是两种选择,不需要为了同一次扩容连续执行两遍。
1 | |
--cluster-use-empty-masters 让尚未持有槽的新主节点参与平衡。rebalance 按槽数和权重计算目标分配,不会按 Key 大小或访问热度计算负载。参数定义可参考 Redis CLI 实现。
2.4 给新主节点配置副本并验收
可以在迁移前为 D 添加副本,也可以在迁移后补齐;需要同步检查副本复制状态。
1 | |
--cluster-slave 是命令行保留的参数名,这里的节点角色为 replica。add-node 不会替新主节点创建副本实例。
3. 缩容流程:4 个主节点缩回 3 个
假设 A、B、C、D 各有 4096 个槽,准备删除 D,可以规划如下迁移:
1 | |
迁移后 A、B、C 分别持有 5461、5461、5462 个槽,D 的槽数为 0。
3.1 把待删除节点的所有槽迁出
先检查剩余节点的内存和吞吐容量,再分批执行:
1 | |
每次执行后核对剩余槽数,实际数量按集群当前状态调整。槽内 Key 会随迁移一起转移,不能只删除槽映射。
3.2 处理副本并确认节点为空
如果 D 有副本,可以把副本挂到保留的主节点,也可以根据容量规划移除它。重新指定复制关系的示例:
1 | |
更换主节点可能触发全量同步,应检查新复制链路。随后检查 D 的本地状态:
1 | |
确认 D 没有槽、没有残留迁移状态,且 DBSIZE 为 0。若槽已迁完但本地仍有 Key,需要排查残留数据的槽归属。
3.3 移除空节点
1 | |
管理工具通知集群忘记 D。确认其他节点的拓扑中已无 D 后,再停止其 Redis 实例,并移除应用中的旧入口地址。删除仍有槽的主节点前,必须先完成重新分片。官方删除节点流程
4. 底层原理:一个槽如何从 A 迁移到 D
以槽 1000 为例,运维工具需要协调数据迁移和槽归属更新。整个槽的传统迁移由多条命令组成,期间槽内 Key 可以分别存在于 A 和 D。
4.1 先准备目标节点,再标记源节点
1 | |
IMPORTING 表示 D 准备从 A 接收槽内数据;MIGRATING 表示 A 正在把槽迁给 D。先设置 D,才能让它接住 A 随后返回的 ASK 重定向。
此时 A 仍是槽的正式所有者。设置迁移状态不会立刻把整个槽的归属切到 D。
4.2 分批迁移槽内 Key
在 A 上循环执行:
1 | |
MIGRATE 在源节点序列化 Key,在目标节点恢复数据,收到成功响应后从源节点删除。正常迁移会保留 Key 的值和过期语义。空字符串与 KEYS 用于批量迁移,0 是目标数据库,5000 是传输空闲超时的毫秒数;Cluster 只使用数据库 0。MIGRATE 文档
这里不使用 COPY,因为需要清空源节点的数据。MIGRATE 超时可能导致两端都保留 Key,处理错误时应核对两端状态;不能把超时直接理解成“目标端没有写入”。
循环到 CLUSTER GETKEYSINSLOT 1000 100 返回空结果,或 CLUSTER COUNTKEYSINSLOT 1000 为 0,再更新槽归属。
4.3 更新槽位所有权
1 | |
D 确认成为槽的新所有者后,A 才放弃归属。相关节点清除迁移状态,并通过集群通信传播配置。若先让 A 放弃槽,而 D 尚未确认归属就发生故障,可能留下没有所有者的槽。
上述顺序和状态转换见 CLUSTER SETSLOT 文档。实际操作优先使用 redis-cli --cluster reshard,由工具协调这些步骤。
5. 在线迁移期间,客户端如何读写
客户端缓存“槽 → 主节点”的路由表。槽 1000 正在从 A 迁往 D 时,客户端可能仍把请求发给 A。
| 请求情形 | 源节点 A 的行为 | 客户端后续处理 |
|---|---|---|
| 单 Key 仍存在于 A | 在 A 执行读写 | 本次请求完成 |
| 单 Key 在 A 上不存在 | 返回 ASK,指向 D |
在 D 的同一连接先发 ASKING,再重发该命令 |
| 槽归属已切到 D,请求仍发给 A | 返回 MOVED,指向 D |
更新槽路由并重发命令 |
| 同槽多 Key 只部分存在于 A | 返回 TRYAGAIN |
等待后按客户端策略重试 |
迁移中的源节点根据 Key 是否存在来处理请求。Key 已迁走和新 Key 在 A 上都不存在,因此都会转向 D;已有 Key 留在 A 时,其更新仍在 A 执行,迁移时再把当前值转到 D。
ASK 只改变本次请求的目的地,客户端保留原槽路由。MOVED 表示槽归属已发生变化,客户端需要修正路由。D 在 IMPORTING 状态下要求客户端先发送 ASKING,避免客户端绕过迁移协议把尚未迁移的 Key 写到 D。
多 Key 命令本来就要求 Key 位于同一槽;传统迁移还可能把同槽 Key 暂时分散到两个节点,因此客户端需要处理 TRYAGAIN。Hash Tag 解决同槽问题,不能消除传统迁移期间的这种情况。SETSLOT 的请求处理规则
在线重新分片允许集群继续服务。迁移仍会消耗 CPU、网络和内存,传统 MIGRATE 还会在传输期间阻塞相关实例;大 Key 迁移尤其容易抬高请求延迟。应按应用的延迟目标调整批量,并监控客户端超时和重试。MIGRATE 的执行行为
6. 分配策略与迁移验收
6.1 槽数均衡与负载均衡
相同容量节点可以先按槽数均分,但槽内 Key 数、数据大小和访问频率可能差异很大。评估迁移效果时,需要一起看槽数、内存占用和请求负载。
不同容量的节点可以设置权重。例如 D 的权重为 2,其他节点为默认的 1,则 D 的目标槽数约占总数的 2 / 5:
1 | |
检查计划后去掉 --cluster-simulate 执行。权重表达容量规划,工具仍按槽数平衡。单个热 Key 或大 Key 无法通过增加节点拆成多个分片,需要应用调整数据模型。
6.2 完成标准
1 | |
验收时检查:
cluster_state:ok,cluster_slots_assigned:16384,cluster_slots_ok:16384。cluster_slots_pfail:0、cluster_slots_fail:0,全部槽有明确归属,节点对槽配置的认知一致。- 传统迁移中没有残留
MIGRATING / IMPORTING状态。需要在各节点查看CLUSTER NODES,它只展示本节点的迁移状态。 - 保留主节点的副本连接正常,复制进度符合预期;应用可以连到新的槽所有者。
- 请求延迟、内存和节点负载符合扩缩容后的容量规划。
如果迁移中断,先检查源端和目标端的 Key、槽归属及迁移状态,再决定继续迁移还是修复。redis-cli --cluster fix 会修改集群状态,应在确认异常原因和修复方案后使用。
7. 新版原子槽迁移
Redis 8.4 引入 CLUSTER MIGRATION,支持服务端原子槽迁移。在目标主节点上可以发起导入并查询任务状态:
1 | |
该机制与逐 Key 的传统迁移不同,服务端按原子槽迁移协议完成任务。IMPORT 返回任务 ID;需要查询任务完成状态后再验收,而不是收到 ID 就认为迁移结束。CLUSTER MIGRATION 文档
Redis 8.10 的发布说明记录了 redis-cli --cluster reshard 和 rebalance 接入服务端原子槽迁移的变化。实际使用时核对服务端版本和 redis-cli 版本;第 4、5 节描述的是传统机制,不能套用到全部新版迁移实现。Redis 8.10 CLI 变更