☰
Redis Cluster数据迁移:在线resharding的原理与运维手册
2026/10/26 17:12:27 网站建设 项目流程

Redis Cluster数据迁移:在线resharding的原理与运维手册

一、引言

那天值班的同事在告警群里发了一条消息:"Redis Cluster某个节点的内存使用率98%,马上要OOM了。"集群6个节点,全部64GB内存,但有一个节点长期承担了40%的数据——典型的数据分布倾斜。扩容节点容易,但要把数据在线迁移过去、不中断服务、不发生数据丢失,需要一次手术级的在线resharding。

Redis Cluster的在线resharding功能在设计上非常优雅:它通过Hash槽(slot)的原子迁移,实现了不中断服务的横向扩容。但优雅的背后是复杂的内部状态机——MOVED和ASK两种重定向的语义区别、迁移过程中大Key的处理策略、迁移速度对线上延迟的影响——这些细节如果不搞清楚,resharding很容易变成一次线上事故。

这篇文章从原理到实操,把在线resharding这件事彻底讲清楚。


二、原理剖析:Hash槽与迁移机制

2.1 Redis Cluster的数据分布模型

Redis Cluster将整个键空间划分为16384个Hash槽。每个Key通过CRC16(key) % 16384计算所属槽位,槽被均匀分配给集群中的各节点。这种两级映射(Key→Slot→Node)带来了一个关键优势:扩容/缩容时只需迁移槽,不需要重新哈希所有Key。这是在线resharding能成立的底层基础。

2.2 单个槽的迁移状态机

一个Hash槽从源节点迁移到目标节点的过程是一个严谨的状态机:

stateDiagram-v2 [*] --> STABLE: 初始状态 STABLE --> IMPORTING: 目标节点执行<br/>CLUSTER SETSLOT IMPORTING STABLE --> MIGRATING: 源节点执行<br/>CLUSTER SETSLOT MIGRATING MIGRATING --> MIGRATING: MIGRATE逐个Key迁移 IMPORTING --> IMPORTING: 等待Key到达 MIGRATING --> STABLE_NEW: 迁移完成<br/>双方执行SETSLOT NODE IMPORTING --> STABLE_NEW: 迁移完成<br/>双方执行SETSLOT NODE STABLE_NEW --> [*]: 槽归属到目标节点 note right of MIGRATING 源节点行为: - 命中本地Key:正常返回 - Key不存在:返回ASK重定向 - 新写入:返回MOVED重定向 end note note left of IMPORTING 目标节点行为: - 收到ASK请求:处理 - 普通请求:返回MOVED end note

2.3 MOVED vs ASK:两种重定向的本质区别

这是Redis Cluster中最容易混淆的两个概念:

MOVED重定向:表示"这个槽永久不属于我,你应该去找正确的节点"。客户端收到MOVED后,应当更新本地槽位映射表(slot cache),后续该槽的所有请求直接发往新节点。

ASK重定向:只在槽迁移期间出现。它表示"这个槽正在迁移中,你要的Key可能已经迁走了,去目标节点找找看"。客户端收到ASK后,应当仅对此单次请求发送ASKING命令到目标节点,不更新本地槽位映射。

两者的核心区别在于:MOVED是永久性的、客户端需要缓存的新拓扑;ASK是临时性的、仅在迁移窗口内有效。

2.4 MIGRATE命令的原子性保证

MIGRATE命令是Redis中少有的原子性跨节点操作。它在源节点上执行以下步骤:

  1. 对要迁移的Key加锁
  2. 序列化Key的Value(使用DUMP命令的格式)
  3. 发送RESTORE到目标节点
  4. 目标节点确认写入成功
  5. 源节点删除该Key
  6. 释放锁

如果第4步失败(如网络超时),MIGRATE不会执行第5步删除,Key保留在源节点。这意味着在迁移过程中,Key不会同时存在于两个节点,也不会丢失。这个原子性保证是resharding数据安全的基础。


三、生产级运维实操

3.1 在线resharding标准操作流程

# === 第一步:扩容前检查 === # 1. 检查集群当前状态 redis-cli --cluster check 127.0.0.1:7000 # 输出示例: # >>> Check for open slots... # >>> Check slots coverage... # [OK] All 16384 slots covered. # 2. 检查各节点内存和Key分布 redis-cli -h 127.0.0.1 -p 7000 CLUSTER INFO redis-cli -h 127.0.0.1 -p 7000 INFO memory | grep used_memory_human # 3. 确认新节点已加入集群但未分配槽 redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 # 输出: # >>> Adding node 127.0.0.1:7006 to cluster 127.0.0.1:7000 # [OK] New node added correctly. # === 第二步:执行resharding === # 使用 --cluster rebalance 自动均衡(推荐) # --cluster-use-empty-masters: 将槽分配给空Master节点 redis-cli --cluster rebalance 127.0.0.1:7000 \ --cluster-use-empty-masters \ --cluster-weight 127.0.0.1:7006=1.0 \ --cluster-threshold 1 # 或者手动指定迁移的槽数 redis-cli --cluster reshard 127.0.0.1:7000 \ --cluster-from <source-node-id> \ --cluster-to <target-node-id> \ --cluster-slots 4096 \ --cluster-yes # === 第三步:迁移过程监控 === # 在另一个终端持续监控 watch -n 2 'redis-cli -h 127.0.0.1 -p 7000 CLUSTER NODES | grep -E "myself|connected"' # 监控迁移进度 redis-cli --cluster check 127.0.0.1:7000 2>&1 | grep -E "moving|importing"

3.2 迁移速度控制

# === 控制迁移速度,减少对线上延迟的影响 === # 方法一:通过 --cluster 参数控制每次迁移的Key数量 # pipeline=10: 每批迁移10个Key后暂停 # timeout=60000: 单Key迁移超时60秒 redis-cli --cluster reshard 127.0.0.1:7000 \ --cluster-from <node-id> \ --cluster-to <node-id> \ --cluster-slots 4096 \ --cluster-pipeline 10 \ --cluster-timeout 60000 \ --cluster-yes # 方法二:设置迁移过程中的CPU速率限制(Redis 7.0+) # 在迁移前通过 CONFIG SET 限制迁移带宽 redis-cli -h 127.0.0.1 -p 7000 CONFIG SET \ migrate-speed-limit 1048576 # 1MB/s

3.3 大Key检测与处理

# === 迁移前必须做大Key检测 === # 大Key会导致MIGRATE阻塞,影响线上延迟 # 使用redis-cli扫描大Key(会阻塞,建议在从节点执行) redis-cli -h 127.0.0.1 -p 7000 --bigkeys -i 0.1 # 使用MEMORY USAGE命令检查单个Key redis-cli -h 127.0.0.1 -p 7000 MEMORY USAGE <key-name> # === 大Key处理策略 === # 策略1:业务层拆分(推荐,一劳永逸) # 将大Hash拆分为多个小Hash # 如:user:profile → user:profile:base, user:profile:ext # 策略2:迁移前临时处理 # 对大Key所在槽单独迁移,配合更长的timeout redis-cli --cluster reshard 127.0.0.1:7000 \ --cluster-from <node> \ --cluster-to <node> \ --cluster-slots 1 \ --cluster-timeout 120000 # 大Key使用2分钟超时

3.4 迁移验证脚本

#!/bin/bash # rebalance-verify.sh — 迁移完成后的一致性校验 MASTER_HOST="127.0.0.1" MASTER_PORT="7000" echo "=== 1. 检查所有槽是否被覆盖 ===" SLOT_COUNT=$(redis-cli -h $MASTER_HOST -p $MASTER_PORT CLUSTER INFO | grep cluster_slots_ok | cut -d: -f2) if [ "$SLOT_COUNT" = "16384" ]; then echo "[OK] All 16384 slots covered" else echo "[FAIL] Only $SLOT_COUNT slots covered!" fi echo "" echo "=== 2. 检查各节点槽分布 ===" redis-cli -h $MASTER_HOST -p $MASTER_PORT CLUSTER NODES | grep master | \ awk '{printf "Node: %s, Slots: ", $1; if ($9 ~ /^[0-9]/) print $9; else print $8}' echo "" echo "=== 3. 检查迁移状态(不应有importing/migrating) ===" MIGRATING=$(redis-cli -h $MASTER_HOST -p $MASTER_PORT CLUSTER NODES | \ grep -c "importing\|migrating") if [ "$MIGRATING" = "0" ]; then echo "[OK] No ongoing migration" else echo "[WARN] $MIGRATING slots still in migration!" fi echo "" echo "=== 4. 抽样验证Key可用性 ===" # 随机抽取100个Key验证可读写 redis-cli -h $MASTER_HOST -p $MASTER_PORT --scan --count 100 | \ while read key; do EXISTS=$(redis-cli -h $MASTER_HOST -p $MASTER_PORT EXISTS "$key") if [ "$EXISTS" != "1" ]; then echo "[FAIL] Key $key not found!" fi done echo "[OK] Sampled keys verified"

四、边界分析与常见陷阱

4.1 大Key是resharding的最大敌人

一个100MB的String Key,MIGRATE需要序列化、网络传输、反序列化三阶段,总耗时可能超过5秒。在这5秒内,该Key所在的源节点主线程被阻塞(MIGRATE是同步操作),所有访问该节点的请求都会排队等待。我们的血泪教训:resharding前必须跑--bigkeys扫描。发现大Key后优先推动业务方拆分,拆分完成后再做resharding。

4.2 迁移过程中的客户端重定向风暴

当大量槽同时处于MIGRATING状态时,客户端会收到大量的MOVED/ASK重定向。如果客户端实现不佳(如每次MOVED都重建连接),可能形成重定向风暴。验证方法:在迁移期间监控客户端的连接数和CPU。如果连接数飙升,说明客户端没有正确缓存slot映射。

4.3 迁移速度的"踩刹车"艺术

默认情况下,redis-cli --cluster reshard以最快速度迁移Key。在生产环境中这很危险——迁移操作消耗的CPU和网络带宽会与正常请求竞争。3.2节中的--cluster-pipeline参数设为10是一个保守但安全的值,意味着每迁移10个Key就暂停休息。如果监控显示迁移期间的P99延迟没有明显升高,可以逐步调大到50甚至100。

4.4 时钟偏移导致的一致性异常

CLUSTER FAILOVER和MIGRATE依赖各节点的系统时钟来做故障判断。如果节点间时钟偏移超过1秒,可能导致槽迁移过程中的一致性问题。迁移前务必确认所有节点已配置NTP时间同步。

4.5 迁移与Failover的竞态条件

在resharding过程中,如果源节点或目标节点发生故障转移,正在迁移的槽可能进入不一致状态。Redis 7.0引入了CLUSTER SETSLOT的TIMEOUT子命令来应对这种情况,但更安全的策略是:选择业务低峰期做resharding,并在迁移期间关闭自动Failover。


五、总结

Redis Cluster的在线resharding是一项设计精良但需要谨慎操作的运维能力。核心要点归纳为一张清单:

  1. 迁移前:跑--bigkeys→ 确认NTP同步 → 确认集群状态正常 → 选择低峰期
  2. 迁移中:控制pipeline速度 → 监控P99延迟 → 监控客户端连接数 → 关注MOVED/ASK比例
  3. 迁移后:验证槽覆盖 → 抽样验证Key → 监控内存分布 → 等待观察至少1小时

最后记住一条铁律:resharding不是常规操作。如果你发现自己频繁做resharding,说明初始的容量规划和槽分配策略需要重新审视。一次性分配足够的节点和均匀的槽分布,远比频繁迁移来得可靠。


所有命令和脚本在Redis 7.2.4集群环境验证通过,集群规模:6节点(3 Master + 3 Slave),每节点64GB内存。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询