Redis 作为内存数据库,在生产环境的生命周期中必然面临各种迁移需求:业务增长推动单节点升级为集群架构、机房搬迁需要跨数据中心迁移、服务器老化触发硬件替换,或是云原生转型需要接入 Kubernetes 部署。每一次迁移都牵一发而动全身,数据完整性、服务可用性、迁移效率三者必须同时满足。本文从真实生产场景出发,系统梳理 Redis 数据迁移的九大核心主题,涵盖工具选型、Slot 重分片、BigKey 治理、一致性校验与零停机切换的完整操作流程。
一、Redis 迁移场景分类与选型
1.1 常见迁移场景
生产环境中的 Redis 迁移通常分为三大类:
架构升级:单体应用演进为分布式系统时,单节点 Redis 无法满足容量和并发需求,需要迁移到 Redis Cluster。这是最典型也最复杂的迁移类型,涉及数据分片、Hash Tag 适配和客户端路由改造。
容量扩展:现有 Cluster 集群的某些节点负载过高,需要增加节点重新分配槽位。这种场景相对简单,因为客户端通常无需改动,只需在集群层面执行槽迁移。
跨机房/跨云迁移:包括同城双活、异地灾备、云厂商切换等。这类迁移对网络延迟最敏感,需要在带宽、一致性和切换时间之间取舍。
1.2 迁移工具对比
| 工具 | 适用场景 | 增量同步 | 断点续传 | 开源状态 |
|---|---|---|---|---|
| redis-migrate-tool | 单节点/主从 -> Cluster | 否 | 否 | 已停止维护 |
| redis-shake (v2/v3) | 全场景支持 | 是 | 是 | 阿里云持续维护 |
| redis-port | 各种架构间迁移 | 是 | 否 | 已停止维护 |
MIGRATE 命令 | 小规模、特定 Key | 否 | 否 | 原生支持 |
| CDC (Change Data Capture) | 与业务结合的双写场景 | 是 | 是 | 自建方案 |
工具选型建议:Redis 6.x 及之前版本优先使用 redis-shake v2,Redis 7.x 使用 redis-shake v3。redis-migrate-tool 虽然原理清晰,但自 2017 年后停止更新,不支持增量同步和断点续传,仅适合一次性小规模迁移。MIGRATE 命令在 Cluster 模式下只能迁移非槽位 Key,场景受限。
二、在线热迁移:redis-shake 实战
redis-shake 是阿里云开源的 Redis 数据迁移工具,支持全量同步、增量同步、断点续传,是目前生产环境的首选方案。它基于 RDB 快照解析 + AOF 命令回放实现数据同步。
2.1 安装与配置
# 下载 redis-shake v3(支持 Redis 7.x)
wget https://github.com/alibaba/RedisShake/releases/download/v3.1.11/redis-shake-linux-amd64.tar.gz
tar -xzf redis-shake-linux-amd64.tar.gz
chmod +x redis-shake
redis-shake 使用 TOML 配置文件,下面是一个典型的 单节点 -> Cluster 迁移配置:
[sync_reader]
address = "192.168.1.10:6379"
password = "source_password"
[cluster_shake_writer]
address = ["192.168.2.10:6379", "192.168.2.11:6379", "192.168.2.12:6379"]
password = "target_password"
tls = false
[filter]
# 可选:只迁移特定模式的 Key
key_exists = "none"
# 比如只迁移用户会话数据
# key_prefix = ["session:", "user:"]
2.2 全量 + 增量同步流程
redis-shake 的同步过程分为三个阶段:
全量同步(Full Sync):连接源端执行
PSYNC或SYNC,接收并解析 RDB 文件,批量写入目标端。对于 Cluster 目标端,shake 会自动根据 Key 的 CRC16 值计算所属槽位,路由到正确的节点。增量同步(Continue):全量完成后,进入实时命令回放模式。源端的每一条写命令都通过源端发送的 AOF 流直接转发到目标端。
优雅的断开:当需要切换业务流量时,先停止源端写入(或进入只读模式),确认增量延迟为 0 后,停止 redis-shake。
# 启动迁移(后台运行建议配合 nohup/systemd)
./redis-shake sync.toml
# 监控同步状态
# 日志中会输出 processed_bytes、total_bytes、bps 等指标
tail -f redis-shake.log | grep -E "processed|total|bps|sync"
2.3 断点续传与异常恢复
redis-shake v3 支持断点续传。当迁移过程中断(网络闪断、进程重启),重启后会读取本地保存的 RDB 解析进度,仅重新同步断点后的增量数据,无需从头开始全量同步。
# 断点信息保存在本地 workdir 目录
ls -la ./data/
# 包含 parse.offset、sync.offset 等文件
常见异常处理:
- 目标端内存不足(OOM):redis-shake 会持续重试写入,直到目标端触发
maxmemory-policy淘汰策略。迁移前应确保目标端预留足够的内存空间(建议源端数据的 1.5 倍以上)。 - 网络带宽不足:使用
--pipeline参数提高批处理效率,或在非业务高峰期执行全量同步。 - BigKey 导致超时:redis-shake 默认对单个 Key 的写入有超时限制。遇到超大 Hash 或 ZSet 时,可能需要拆分 Key 或调整目标端的
proto-max-bulk-len。
2.4 原生 MIGRATE 命令
对于小规模数据迁移,或需要精确控制单个 Key 的场景,Redis 原生的 MIGRATE 命令是最直接的选择。
# 将源端的 user:1001 迁移到目标端
MIGRATE 192.168.2.10 6379 user:1001 0 5000 AUTH target_password
# 批量迁移匹配前缀的所有 Key(配合 shell 脚本)
redis-cli --scan --pattern "session:*" | while read key; do
redis-cli MIGRATE 192.168.2.10 6379 "$key" 0 5000 COPY AUTH target_password
done
注意:Cluster 模式下,MIGRATE 只能迁移属于当前节点负责的槽位的 Key。如果要跨节点迁移,必须通过客户端连接正确的源节点。因此 MIGRATE 在 Cluster 场景下远不如 redis-shake 便捷。
三、Cluster 模式下的 Slot 迁移与重平衡
3.1 槽位(Slot)迁移原理
Redis Cluster 将 16384 个槽位分配到各主节点。扩容时新增主节点,需要从现有节点迁移部分槽位;缩容时则需要将待下线节点的槽位全部分配给其他节点再执行移除。
槽迁移是 Redis Cluster 的内建能力,整个过程在槽粒度上完成,对业务的影响极小。核心机制如下:
- 目标节点向源节点发送
CLUSTER SETSLOT <slot> IMPORTING <source_node_id>。 - 源节点向目标节点发送
CLUSTER SETSLOT <slot> MIGRATING <target_node_id>。 - 源节点遍历该槽位下的所有 Key,使用
MIGRATE命令批量迁移到目标节点。 - 所有 Key 迁移完成后,双方节点广播槽位归属变更。
迁移期间,属于正在迁移槽位的查询会被重定向(MOVED/ASK 响应),客户端需要正确处理重定向。
3.2 redis-cli –cluster reshard
这是官方推荐的手动重分片命令,适合可控的小规模调整:
# 执行重分片(交互式)
redis-cli --cluster reshard 192.168.1.10:6379
# 非交互式:将 2000 个槽位从源节点迁出,分配给目标节点
redis-cli --cluster reshard 192.168.1.10:6379 \
--cluster-from <source_node_id> \
--cluster-to <target_node_id> \
--cluster-slots 2000 \
--cluster-yes
参数说明:
--cluster-from:源节点 ID,多个用逗号分隔,或输入all从所有现有节点均匀迁出。--cluster-to:目标节点 ID(迁移目的地)。--cluster-slots:要迁移的槽位数量。--cluster-yes:跳过确认提示,自动化脚本使用。
3.3 自动重平衡
当新增多个节点或缩容时,手动逐个 reshard 非常繁琐。redis-cli 提供了自动重平衡功能,将所有槽位按节点数量均匀分配:
# 自动重平衡所有主节点
redis-cli --cluster rebalance 192.168.1.10:6379 \
--cluster-use-empty-masters
# 指定权重(某些节点配置更高,承担更多槽位)
redis-cli --cluster rebalance 192.168.1.10:6379 \
--cluster-weight <node_id1>=5,<node_id2>=3,<node_id3>=2
--cluster-use-empty-masters 的含义是将没有任何槽位的主节点也纳入重平衡计算中。新加入的节点默认没有槽位,只有加上这个参数,rebalance 才会将槽位分配给它们。
3.4 扩容缩容完整流程
新增节点扩容:
# 1. 启动新 Redis 实例并加入集群
redis-cli --cluster add-node 192.168.1.20:6379 192.168.1.10:6379
# 2. 执行自动重平衡
redis-cli --cluster rebalance 192.168.1.10:6379 --cluster-use-empty-masters
# 3. 为新增主节点配置从节点(可选但强烈建议)
redis-cli --cluster add-node 192.168.1.21:6379 192.168.1.10:6379 \
--cluster-slave --cluster-master-id <new_master_id>
缩容移除节点:
# 1. 将目标节点的所有槽位迁出
redis-cli --cluster reshard 192.168.1.10:6379 \
--cluster-from <node_to_remove_id> \
--cluster-to <existing_master_id> \
--cluster-slots 4096 --cluster-yes
# 2. 重复执行直到该节点槽位为 0
# 3. 移除节点
redis-cli --cluster del-node 192.168.1.10:6379 <node_to_remove_id>
3.5 迁移期间的影响与缓解
槽迁移期间,对属于该槽位的 Key 的操作会触发 ASK 重定向。如果客户端没有正确处理 ASKING 命令,可能出现请求失败。
ASK 1337 192.168.1.20:6379
客户端处理要求:
- 收到
-ASK <slot> <ip>:<port>回复后,向目标节点发送ASKING命令。 - 然后重新发送原命令。
- 后续对该槽位的请求可以直接路由到新节点(配合
MOVED更新路由表)。
主流客户端(jedis、go-redis、redis-py-cluster)都内置了重定向处理逻辑,通常无需手动干预。但务必在低峰期执行大规模槽迁移,避免重定向风暴。
四、BigKey 迁移的挑战与解决方案
4.1 BigKey 的危害
BigKey(单 Key 占用大量内存)是 Redis 迁移中最危险的雷区。常见的 BigKey 类型包括:
- 百万级成员的 Hash 或 ZSet
- 巨型 String(序列化后的 JSON 数组,几十 MB)
- 超长 List(消息队列失控堆积)
BigKey 在迁移过程中会导致:
MIGRATE命令阻塞源节点和目标节点的单线程,导致正常请求被挂起。- RDB 生成和传输时间极长,全量同步迟迟无法完成。
DEL命令删除 BigKey 时,释放大内存产生阻塞(Redis 4.0+UNLINK可缓解)。- 集群槽迁移时,单个 BigKey 导致整个槽位迁移超时失败。
4.2 BigKey 扫描与识别
迁移前必须全面扫描 BigKey。redis-cli 提供了专门的扫描命令:
# 扫描每个数据类型的 Top N 大 Key(内存占用)
redis-cli --bigkeys
# 精确扫描:使用 rdb-tools 分析 RDB 文件
pip install rdbtools python-lzf
rdb -c memory /var/lib/redis/dump.rdb > memory.csv
# 按 size_in_bytes 排序,找出占用最大的 Key
sort -t, -k4 -nr memory.csv | head -50
Go 编写的实时扫描脚本:
package main
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
)
func scanBigKeys(ctx context.Context, rdb *redis.Client) error {
var cursor uint64
for {
keys, next, err := rdb.Scan(ctx, cursor, "*", 100).Result()
if err != nil {
return err
}
for _, key := range keys {
size, _ := rdb.MemoryUsage(ctx, key).Result()
if size > 1024*1024 { // 大于 1MB
fmt.Printf("BIGKEY: %s, size: %d bytes\n", key, size)
}
}
cursor = next
if cursor == 0 {
break
}
}
return nil
}
4.3 BigKey 拆分策略
识别出 BigKey 后,迁移前必须治理。常用的拆分策略:
Hash 拆分:将大 Hash 按业务维度拆分为多个小 Hash,或者用 String 序列化替代。
# 改造前:一个 Hash 存储所有用户配置
HGETALL user:1000:config # 返回 10万 个字段
# 改造后:按模块拆分
HGETALL user:1000:basic
HGETALL user:1000:security
HGETALL user:1000:preference
List/Set 分片:按时间窗口或 ID 范围分片。
# 改造前:消息队列全放入一个 List
LPUSH messages $data
# 改造后:按小时/天分桶
LPUSH messages:20250816_14 $data
ZSet 分段:如果 ZSet 按时间排序,可以按月份分段存储。
对于确实无法拆分的 BigKey,迁移时使用 DUMP/RESTORE 替代 MIGRATE 以获得更细粒度的控制:
# 分片恢复大 Key(需要客户端配合分片读取)
redis-cli --eval split_bigkey.lua bigkey target_host target_port
-- split_bigkey.lua
-- 将大 Hash 拆分为小 Hash 后迁移
local key = KEYS[1]
local target = ARGV[1]
local port = tonumber(ARGV[2])
local cursor = "0"
local batch = {}
repeat
local res = redis.call('HSCAN', key, cursor, 'COUNT', 100)
cursor = res[1]
for i = 1, #res[2], 2 do
batch[res[2][i]] = res[2][i+1]
end
if #batch >= 1000 or cursor == "0" then
-- 分批写入目标端(此处简化,实际应使用 MIGRATE 或 RESTORE)
for k, v in pairs(batch) do
redis.call('HSET', key .. ":shard:" .. i, k, v)
end
batch = {}
end
until cursor == "0"
五、双写与影子流量模式
5.1 双写(Dual Write)模式
双写是实现零停机迁移的核心模式:在切换流量前,同时向源端和目标端写入数据,确保两边的数据始终保持一致。
+------------+
写请求 ----->| 业务应用 |
读请求 <-----| |
+------+-----+
|
+------+------+
| |
+----v---+ +-----v----+
| 源 Redis | | 目标 Redis |
| (旧环境) | | (新环境) |
+----------+ +------------+
双写的代码实现示例(Go):
type DualWriter struct {
Source *redis.Client
Target *redis.Client
Mode string // "sync", "async", "shadow"
}
func (d *DualWriter) Set(ctx context.Context, key string, val interface{}) error {
// 1. 写入源端(必须成功)
err := d.Source.Set(ctx, key, val, 0).Err()
if err != nil {
return err
}
// 2. 异步写入目标端(失败不阻塞主流程)
if d.Mode == "async" {
go func() {
_ = d.Target.Set(ctx, key, val, 0).Err()
}()
} else if d.Mode == "sync" {
_ = d.Target.Set(ctx, key, val, 0).Err()
}
return nil
}
三种双写模式:
| 模式 | 原理 | 风险 | 适用阶段 |
|---|---|---|---|
| 同步双写 | 同时写两端,均成功才返回 | 目标端延迟影响源端,或部分失败导致不一致 | 切换前夕 |
| 异步双写 | 源端成功即返回,目标端后台写入 | 切换瞬间可能丢失最近几秒的异步数据 | 迁移预热期 |
| 影子流量 | 读请求同时发往两端,只有源端响应 | 不写入目标端,仅用于对比延迟和正确性 | 压测验证期 |
5.2 双写的数据一致性问题
双写最大的风险是两边数据不一致。常见的陷阱:
超时写:目标端写入超时但源端已成功。应用如果单纯重试,可能导致源端重复写入。应使用幂等性设计,或在重试前确认目标端状态。
部分失败:批量命令(Pipeline)中部分成功。使用
MULTI/EXEC事务保证原子性,但事务在 Cluster 模式下要求所有 Key 位于同一槽位。时序依赖:依赖先读后写的逻辑在双写环境下可能乱序。例如
INCR计数器在不同节点的执行顺序可能不一致。
解决方案:对顺序敏感的操作,在双写期间强制走源端,切换完成后再放开。
5.3 影子流量验证
影子流量(Shadow Traffic)是指将生产请求同时镜像到目标环境,但不返回目标端的结果给客户端,仅用于观察和对比。这是验证迁移正确性的最佳手段。
func (d *DualWriter) Get(ctx context.Context, key string) (string, error) {
// 主流程:从源端读取
val, err := d.Source.Get(ctx, key).Result()
if err != nil {
return "", err
}
// 影子流程:从目标端读取并比对(异步,不影响响应)
go func() {
shadowVal, shadowErr := d.Target.Get(ctx, key).Result()
if shadowErr != nil {
metrics.Inc("shadow_read_error")
} else if shadowVal != val {
metrics.Inc("shadow_read_mismatch")
log.Warnf("shadow mismatch: key=%s source=%s target=%s", key, val, shadowVal)
}
}()
return val, nil
}
影子流量验证期通常持续 24-72 小时,期间需要监控比对差异率。若差异率为 0%,说明目标端完全可用,可以安全切换。
六、数据一致性验证方法
6.1 整体校验和对比
迁移完成后,最核心的步骤是验证数据一致性。校验和(Checksum)是最常用的方法,但 Redis 并不直接提供数据库级别的校验和。
基于 Key 的抽样校验:
#!/bin/bash
# checksum_compare.sh:对比源端和目标端指定 Key 的 SHA256
SOURCE_HOST=$1
TARGET_HOST=$2
KEY=$3
source_val=$(redis-cli -h $SOURCE_HOST GET "$KEY")
target_val=$(redis-cli -h $TARGET_HOST GET "$KEY")
source_hash=$(echo -n "$source_val" | sha256sum | awk '{print $1}')
target_hash=$(echo -n "$target_val" | sha256sum | awk '{print $1}')
if [ "$source_hash" == "$target_hash" ]; then
echo "MATCH: $KEY"
else
echo "MISMATCH: $KEY (source=$source_hash target=$target_hash)"
fi
6.2 批量抽样校验
对于百万级 Key 的集群,逐条比对不现实。应采用分层抽样策略:
#!/bin/bash
# 从每个槽位随机抽样若干 Key 进行比对
for slot in $(seq 0 16383); do
# 随机获取该槽位的一个 Key
key=$(redis-cli -h source CLUSTER GETKEYSINSLOT $slot 1 | head -1)
if [ -n "$key" ]; then
type=$(redis-cli -h source TYPE "$key")
case $type in
string)
s=$(redis-cli -h source GET "$key")
t=$(redis-cli -h target GET "$key")
[ "$s" == "$t" ] || echo "MISMATCH string: $key"
;;
hash)
s=$(redis-cli -h source HGETALL "$key" | sort)
t=$(redis-cli -h target HGETALL "$key" | sort)
[ "$s" == "$t" ] || echo "MISMATCH hash: $key"
;;
zset)
s=$(redis-cli -h source ZRANGE "$key" 0 -1 WITHSCORES)
t=$(redis-cli -h target ZRANGE "$key" 0 -1 WITHSCORES)
[ "$s" == "$t" ] || echo "MISMATCH zset: $key"
;;
esac
fi
done
6.3 RDB 级别对比工具
对于全量迁移的终极验证,可以对比源端和目标端生成的 RDB 文件:
# 1. 在源端和目标端同时执行 BGSAVE
redis-cli -h source BGSAVE
redis-cli -h target BGSAVE
# 2. 等待 save 完成
# 3. 使用 rdb-tools 对比 Key 数量和总大小
rdb -c memory /var/lib/redis/source/dump.rdb | wc -l
rdb -c memory /var/lib/redis/target/dump.rdb | wc -l
# 4. 对比具体 Key 的大小
rdb -c memory /var/lib/redis/source/dump.rdb > source.csv
rdb -c memory /var/lib/redis/target/dump.rdb > target.csv
diff source.csv target.csv
6.4 增量校验:redis-full-check
阿里云提供了 redis-full-check 工具,专门用于对比源和目标 Redis 的数据一致性,支持全量对比和增量对比两种模式。
# 全量对比
./redis-full-check -s "192.168.1.10:6379;source_password" \
-t "192.168.2.10:6379;target_password" \
--comparetype=1 --qps=1000
# 参数说明:
# --comparetype=1 全量对比
# --comparetype=2 增量对比(基于 checksum,速度快)
# --qps 限制对比速度,避免影响线上
检查结果分为三类:
Keylack:目标端缺少某个 Key。ValueNotEqual:两端 Key 的值不一致。TypeNotEqual:两端 Key 的类型不一致。
对于少量不一致的 Key,需要排查是否在对比窗口期内发生了写入(即还未同步到目标端),或是双写逻辑存在 Bug。
七、零停机迁移完整操作流程
7.1 迁移前准备清单
一个严谨的零停机迁移需要完成以下准备工作:
- 容量评估:目标环境内存 >= 源端数据量 x 1.5(预留缓冲和 BigKey 膨胀)。
- 网络测试:源端与目标端之间的网络延迟和带宽。跨机房时建议使用
iperf3测试。 - BigKey 扫描:使用
--bigkeys和 rdb-tools 全面扫描,提前治理。 - 客户端兼容性:确认客户端支持 Cluster 协议(
MOVED/ASK重定向)。 - 监控告警:在目标端预先部署监控,包括内存、QPS、延迟、连接数。
- 回滚方案:确保可以在切换后快速回退到源端。
# 网络带宽测试
iperf3 -c target_host -t 60 -i 5
# 预估全量同步时间(按 100MB/s 计算)
# 数据量 50GB / 100MB/s = 500 秒 ≈ 8.3 分钟
redis-cli -h source INFO memory | grep used_memory_human
7.2 分阶段切换流程
阶段一:影子验证(1-3天)
-> 启动 redis-shake 全量同步
-> 进入实时增量同步
-> 开启影子流量,对比读写结果
-> 目标端压测确认性能满足要求
阶段二:双写预热(1-3天)
-> 停止影子流量
-> 开启双写(先异步,后同步)
-> 监控双写延迟和差异率
-> 全量一致性校验通过
阶段三:流量切换
-> 将源端设为只读(stop-writes-on-bgsave-error yes 或 config set)
-> 确认增量同步延迟为 0
-> 停止 redis-shake
-> 修改客户端配置,流量切到目标端
-> 观察业务量健康度
阶段四:观察期(3-7天)
-> 保持源端运行,随时可回滚
-> 确认目标端稳定
-> 下线源端并归档
7.3 回滚方案
迁移过程中发生严重问题时必须能快速回滚。回滚的核心是确保源端在整个切换前后都保持一致状态。
# 回滚操作(假设目标端已写入新数据且需要回滚)
# 1. 将目标端最新的增量数据同步回源端
# 2. 切换客户端回到源端
# 3. 源端恢复写入
# 源端设为只读(切换前设置,便于回滚)
redis-cli -h source CONFIG SET slave-read-only no
# 注意:主节点没有 slave-read-only 配置,如果源端是单节点
# 可以在应用层通过开关控制写操作路由
对于使用双写模式的情况,回滚最简方案是修改客户端路由配置,将写操作指回源端。由于双写期间源端数据也是最新的,业务可以立即恢复。
八、跨区域复制与异地多活
8.1 跨机房复制方案
跨境/跨区域的 Redis 部署面临的核心矛盾是延迟。Redis 的复制是异步的,跨机房复制延迟通常在 10-100ms 之间,因此原生主从复制只适合灾备场景,不适合双活写入。
方案一:主从跨机房复制(冷备)
北京机房(主) 上海机房(从)
Redis Master -----> Redis Slave
写入 + 读取 只读 / 灾备
配置要点:开启 repl-diskless-sync yes,使用无盘复制避免磁盘 I/O 瓶颈。跨公网时建议开启 TLS:tls-replication yes。
方案二:双向复制(CRDT)
开源社区提供了基于 CRDT(Conflict-free Replicated Data Types)的双向复制方案,如 Redis Enterprise 的 Active-Active 或阿里的 Tair。CRDT 允许双机房同时写入,自动解决冲突。
北京机房 上海机房
Redis A <=============> Redis B
(CRDT 协议) (CRDT 协议)
CRDT 的局限是并非所有 Redis 数据类型都能天然支持冲突消解。String 的 SET 操作在冲突时无法自动合并,需要业务层定义冲突解决策略(如 Last-Write-Wins 或向量时钟)。
8.2 全球负载均衡与就近读取
对于跨国业务,可以在多个区域部署 Redis 从节点,配合 DNS 或 CDN 将用户请求路由到就近节点读取。写入则统一路由到主库所在区域。
用户请求
|
+-----+-----+
| |
美国用户 中国用户
| |
DNS 路由 DNS 路由
| |
US-Redis CN-Redis
Slave Slave
| |
+-----+-----+
|
Master Redis
(主写)
此方案的 RPO(恢复点目标)取决于复制延迟,RTO(恢复时间目标)取决于 DNS TTL 和客户端重连速度。对于数据一致性要求极高的金融交易场景,RPO 趋近于 0 意味着不能使用异步复制,必须引入同步确认机制,但这会大幅牺牲性能。
8.3 跨云迁移
跨云迁移(例如从阿里云迁到 AWS ElastiCache 或自建 IDC)的核心挑战是网络连通性和云厂商的差异化特性。
公网 Tunnel + redis-shake:
# 通过 SSH Tunnel 打通跨云网络
ssh -fN -L 6380:source-redis.internal:6379 bastion.host.com
# redis-shake 配置指向本地 Tunnel 端口
[sync_reader]
address = "127.0.0.1:6380"
password = "source_password"
注意事项:
- 公网传输必须启用 TLS,防止数据泄露。
- 不同云厂商的 Redis 服务可能有协议差异(如 AWS ElastiCache 禁用了某些管理命令),迁移前需要逐一确认。
- Cluster 配置端点(Configuration Endpoint)在不同云中的实现方式不同,切换时需要客户端适配。
九、Redis on Kubernetes 部署与迁移
9.1 StatefulSet 部署方案
在 Kubernetes 上部署 Redis,StatefulSet 是管理有状态服务的标准选择。每个 Pod 拥有稳定的网络标识和独立的持久存储。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis-cluster
spec:
serviceName: redis-cluster
replicas: 6
selector:
matchLabels:
app: redis-cluster
template:
metadata:
labels:
app: redis-cluster
spec:
containers:
- name: redis
image: redis:7.2-alpine
ports:
- containerPort: 6379
name: redis
- containerPort: 16379
name: cluster
command:
- redis-server
- /etc/redis/redis.conf
volumeMounts:
- name: data
mountPath: /data
- name: config
mountPath: /etc/redis
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
9.2 Headless Service 与节点发现
Cluster 模式要求每个节点都有固定的、可被其他节点访问的地址。在 K8s 中通过 Headless Service 实现:
apiVersion: v1
kind: Service
metadata:
name: redis-cluster
spec:
clusterIP: None # Headless Service
selector:
app: redis-cluster
ports:
- port: 6379
name: redis
- port: 16379
name: cluster
创建 Headless Service 后,每个 Pod 的 DNS 解析形如 redis-cluster-0.redis-cluster.default.svc.cluster.local,其他节点通过这个地址相互发现。
9.3 初始化集群
部署完 6 个 Pod(3 主 3 从)后,需要在一个临时 Pod 中执行 redis-cli --cluster create:
kubectl run redis-init --rm -i --restart=Never \
--image=redis:7.2-alpine -- \
redis-cli --cluster create \
redis-cluster-0.redis-cluster:6379 \
redis-cluster-1.redis-cluster:6379 \
redis-cluster-2.redis-cluster:6379 \
redis-cluster-3.redis-cluster:6379 \
redis-cluster-4.redis-cluster:6379 \
redis-cluster-5.redis-cluster:6379 \
--cluster-replicas 1 --cluster-yes
9.4 Redis Operator 方案
手动管理 StatefulSet + Headless Service 足以运行 Redis,但扩缩容、故障恢复仍需要人工介入。Operator 模式将运维知识编码为控制器,实现 Redis 集群的自动化运维。
常用 Redis Operator:
| Operator | 维护方 | 特性 |
|---|---|---|
| Redis Operator (Spotahome) | 社区 | Cluster 管理、自动故障转移 |
| Redis Enterprise Operator | RedisLabs | 官方企业级方案 |
| Alibaba Redis Operator | 阿里云 | 深度集成云特性 |
| KubeDB Redis | AppsCode | 多数据库通用方案 |
Spotahome Redis Operator 部署示例:
apiVersion: databases.spotahome.com/v1
kind: RedisFailover
metadata:
name: redis-cluster
spec:
sentinel:
replicas: 3
redis:
replicas: 3
storage:
persistentVolumeClaim:
spec:
resources:
requests:
storage: 10Gi
这个 Operator 自动管理 Sentinel + Redis 主从架构。但原生 Cluster 模式的管理则更加复杂,因为需要处理槽位分配和节点发现的动态性。
9.5 K8s 内外的数据迁移
从外部 IDC 迁入 K8s:
- 在 K8s 中部署目标 Redis Cluster。
- 使用 NodePort 或 LoadBalancer 暴露目标 Redis 的服务端口。
- 在外部 IDC 启动 redis-shake,将数据同步到 K8s 暴露的地址。
- 切换应用流量到 K8s 内部的 Redis 服务。
# Expose Redis Cluster for external migration
kubectl expose statefulset redis-cluster \
--type=NodePort --port=6379 --name=redis-migrate-svc
从 K8s 迁出到云托管服务:
- 对云托管 Redis 开启白名单,允许 K8s 集群出口 IP 访问。
- 在 K8s 内部署 redis-shake 的 Job,将数据同步到云 Redis。
- 验证一致性后,修改应用的 Redis 连接地址为云托管端点。
apiVersion: batch/v1
kind: Job
metadata:
name: redis-migrate
spec:
template:
spec:
containers:
- name: redis-shake
image: registry.aliyuncs.com/aliyun_kube/redis-shake:v3
command: ["./redis-shake", "sync.toml"]
volumeMounts:
- name: config
mountPath: /app/sync.toml
subPath: sync.toml
restartPolicy: Never
9.6 数据持久化与备份在 K8s 中的注意事项
K8s 中 Redis 的持久化面临两个特有挑战:
Pod 漂移与存储绑定:使用 StatefulSet + PVC 可以确保 Pod 重建后绑回同一个 PV,但节点故障导致 Pod 漂移到其他节点时,依赖 CSI 驱动的跨节点挂载能力。
RDB/AOF 与容器存储层:容器存储通常比本地 SSD 性能差。对于写入密集型场景,建议在 K8s 中使用本地存储(Local PV)而非网络存储(NFS/CephFS)。Redis 7.0 的多部分 AOF 在慢存储上能显著降低
fsync的阻塞时间。
# 使用 Local PV(高性能本地 SSD)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-ssd
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
十、总结
Redis 数据迁移从来不是简单的 “dump & restore”,而是涉及工具选型、网络架构、数据校验、业务改造的系统性工程。本文梳理的九大主题形成了完整的迁移方法论:
- 场景判断决定工具选择:小规模用
MIGRATE,大规模用 redis-shake,长期同步用 CDC。 - Cluster 槽位迁移是扩容缩容的核心,掌握
reshard和rebalance命令即可应对大多数集群调整需求。 - BigKey 治理是迁移成功的先决条件,迁移前必须扫描和拆分,否则槽迁移或全量同步都会陷入困境。
- 双写 + 影子流量是零停机切换的黄金组合,前者保证两边数据一致,后者验证目标端正确性。
- 一致性校验不能仅凭抽样,应组合使用 Key 级比对、RDB 工具分析和 redis-full-check 全量扫描。
- 跨区域复制需要容忍延迟,RPO 和 RTO 的平衡要从业务容忍度出发设计。
- K8s 部署通过 StatefulSet + Headless Service 解决有状态问题,Operator 则进一步减轻了运维负担。
无论采用哪种方案,生产环境迁移都遵循一条铁律:先在准生产环境完整演练一遍。数据迁移无法回退,每一次操作都需要可观察、可验证、可回滚。祝你的 Redis 迁移一切顺利。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。