☰
Redis三节点集群无缝迁移实战:redis-shake全量+增量同步方案
2026/10/9 9:15:19 网站建设 项目流程

先说结论:Redis集群做“三节点到三节点”的迁移,难点从来不在数据拷贝,而在于怎么让业务无感知、数据不丢失、回滚有退路。我上个月刚做完一个线上生产环境的同构集群迁移,旧集群三台物理机,新集群三台容器化部署,从方案选型到最终切换花了整整一周。这期间踩了不少坑,也把几条常规迁移路线都过了一遍,最后形成的这套流程在流量高峰期完成了无缝过渡,写下来做个完整复盘。

如果你也遇到机房搬迁、云厂商切换、或者纯粹想把自建集群挪进容器平台这类需求,这篇应该能帮你少走很多弯路。文章会覆盖方案对比、工具选型、参数调优、切换细节、以及几个只有真跑过迁移才会遇到的故障点。

1. 为什么会有“三迁三”这种需求:场景决定方案

1.1 同构集群迁移比异构迁移更考验细节

很多人一听“三节点到三节点”,第一反应是:节点数都一样,直接在新机器上搭好集群,把数据导过去不就行了?但实际生产环境里,“同构”指的只是节点数量相同,不等于环境相同。

我这次迁移的背景是一个自建的物理机Redis Cluster,三个master节点,版本是5.0.14。新集群是容器平台上的三个节点,同样跑Redis Cluster,但版本升级到了6.2.7,而且新集群提前配置了三从架构(每个master带一个slave)。看起来都是三主节点,但底层环境、版本、部署方式全变了。

同构迁移的迷惑性就在这里:因为你不需要重新设计拓扑,所以容易低估工作量,直接想拿RDB文件搬过去。但一旦涉及到版本跨代、部署形态变化、客户端连接方式调整,光靠RDB文件恢复是远远不够的。

1.2 无缝过渡的两个核心指标:数据零丢失与业务零感知

在定方案之前,我和团队先明确了两个硬指标:

  • 数据零丢失:迁移过程中写入Redis的数据不能丢,也就是说不能用“停服备份再恢复”这种简单粗暴的方式。
  • 业务零感知:客户端连接不需要改代码、不需要重启应用,切换过程中接口不能出现明显超时或报错。

这两个指标看着容易,实际上已经把“停机迁移”这条路堵死了。剩下的方案只有两条路线:一条是增量同步 + 快速切换(准无缝),另一条是业务双写 + 最终切换(真无缝)。两条路线我都仔细梳理了,下文会逐一说清取舍。

还有一个隐性问题值得提前强调:Redis Cluster的客户端是有拓扑感知的,切换集群后如果客户端还连着旧节点的IP,会出现大量MOVED重定向,这个问题会在切换阶段专门处理,后面详细讲。

2. 四条常规迁移路线,为什么最后选了同步工具

2.1 临时主从挂载:哨兵架构很顺,cluster架构别扭

最常见的无感迁移思路是“新旧搭主从”。如果是Redis Sentinel架构(一主一从一哨兵之类),在旧master上执行REPLICAOF指向新master,同步完成后把哨兵指向新集群,然后执行REPLICAOF NO ONE,整个过程非常顺滑。

但Redis Cluster架构下,这套玩法会碰壁。Cluster模式下每个节点有固定的slot分配,新节点的master身份、slot归属都要通过集群协议协商。如果你让新集群某个master执行replicaof指向旧集群的master,在cluster-enabled状态下,它会尝试在集群拓扑中重新协商身份,很容易出现“自己把自己搞成孤儿节点”的诡异状态,或者数据能复制过来但集群状态一直是cluster_state:fail。

我实际测试了一下,新节点执行replicaof指向旧集群节点后,数据确实开始同步了,但新集群因为某些slot归属跟旧集群的配置信息不一致,集群状态直接变成fail。排查起来很费劲,而且就算解决,切换时的一连串cluster指令操作也远不如工具链成熟。所以这个方案直接放弃了——不是说完全不行,而是坑太密,性价比太低。

2.2 MIGRATE逐key搬迁:只适合小数据量

Redis自带的MIGRATE命令可以把key从一个节点原子迁移到另一个节点,Cluster模式下还能指定slot迁移。但它是逐key操作的,每迁移一个key都要建立一次到目标节点的连接(可以开pipeline优化,但本质上还是一个key一个key搬)。

我这边旧集群有差不多800万key,最大单key有几十MB,用MIGRATE搬一轮,算下来大概要跑十几个小时,而且中间如果业务还在写,新写入的key还得扫第二遍。更关键的是,MIGRATE只搬数据和TTL,不搬集群配置、不搬slot归属,迁移完成后你还得手动做客户端拓扑切换。这个方案只适合那种总共几十万key、业务量很小的内部系统,生产核心链路完全不适合。

2.3 新节点入旧集群再reshard:集群重构不等于迁移

还有一条路是“让新节点加入旧集群,用redis-cli --cluster reshard把slot迁过去,最后把旧节点摘掉”。这个方案在技术上是可行的,本质是集群扩容再缩容。

但这里有个很现实的问题:大规模reshard过程中,Redis Cluster的slot迁移会触发大量的ASK重定向请求,对客户端有一定兼容性要求;而且迁移期间旧节点的内存和CPU都会飙升,如果集群负载本来就高,会直接影响线上延迟。再加上这个方案要求新旧节点网络能够互通(双向),在跨机房、跨云场景下基本不具备条件。

我这次新旧集群不在同一网段,虽然网络策略可以做双向打通,但运维和安全那边流程很长,最终还是放弃了这条需要网络深通的路线。如果你新旧集群在一个二层网络里,这个方案倒是可以考虑,但要严格在低峰期做。

2.4 redis-shake全量+增量:可控性最强的组合

最后我把目光落在redis-shake上。它是阿里云开源(现在由tair团队维护)的Redis数据同步工具,支持standalone、cluster、proxy等多种形态之间互相同步,核心能力就是全量同步(基于RDB解析)加增量同步(基于PSYNC命令订阅后续写入)。

为什么选它而不是自己写脚本?三个理由:

  • 全量阶段它会把源RDB文件解析后按命令格式灌入目标端,天然避开了RDB版本兼容问题,而且会保留TTL。
  • 增量阶段它用PSYNC协议持续抓取源实例的新写入,我可以在切换前让增量一直跑着,把数据差距压到几百毫秒以内。
  • 它支持配置文件启动多个同步任务,我可以为每个源master单独起一个同步进程,做到“一对一”精确映射。

这套组合让我能在新集群不停机的情况下,先同步一个“和历史数据基本一致、且持续追平增量”的数据副本,然后再挑一个业务低峰窗口做快速切换。这就是我要的“准无缝”方案。

3. redis-shake迁移的落地过程与参数实证

3.1 新集群的初始化细节

新集群的部署我不过多展开,但有几个前置条件必须做对,否则后面同步过程会出幺蛾子:

  • 新集群不要预先写任何业务数据,保持三个master节点的数据都是空的。
  • 新集群的每个master都要提前配好对应的slave,因为redis-shake同步的是master节点,流量切换后如果新master没有从节点保护,一旦宕机整个集群不可写。
  • 新集群的密码认证、AOF持久化都要先配置好。特别提醒:开启AOF后再接收全量同步,会比只靠RDB更稳,但落地时要注意磁盘空间和fsync策略,避免同步高峰期把磁盘IO打满。

我这边新集群三个master的IP规划是:10.0.8.11:6379、10.0.8.12:6379、10.0.8.13:6379,旧集群是192.168.5.21:6379、192.168.5.22:6379、192.168.5.23:6379。redis-shake部署在旧的物理机上,走内网连接新集群。

3.2 全量同步阶段的参数调优

redis-shake的配置文件是redis-shake.conf,我用的是sync模式跑“全量+增量”链路。核心配置如下:

source.type = cluster source.address = 192.168.5.21:6379;192.168.5.22:6379;192.168.5.23:6379 source.password = old_password source.auth-type = auth target.type = cluster target.address = 10.0.8.11:6379;10.0.8.12:6379;10.0.8.13:6379 target.password = new_password target.auth-type = auth sync.mode = sync # 大key自动拆分阈值 advanced.big-key-threshold = 104857600 # 单个pipeline的批量大小 advanced.pipeline-count-limit = 8 # 日志级别 log.level = info

几个参数我在实际压测后调整过,这里说说为什么:

  • advanced.big-key-threshold:默认是102400字节(约100KB)。我这边有不少session缓存key比较大,如果按默认阈值,大量key都会走拆分路径,线程调度会频繁切换,干脆调大到100MB,只有真正的大key才做拆分批处理。这里的教训是:不要迷信默认值,要根据你的key大小分布来设。
  • advanced.pipeline-count-limit:控制单次pipeline处理命令的数量。改小会让单批传输更快完成但总吞吐下降,改大吞吐高但风险缓冲区变大。我摩擦了三四轮,最终稳定在8比较合适。
  • 另外建议关闭advanced.resolve(做DNS预解析)在容器网络环境下的一些隐性问题,直接走IP最稳。

三个同步任务我是分三个进程跑的,每个进程同步一个旧master到对应的新master。这里有个关键点:三个进程的顺序性没有严格要求,但最好错开启动时间,避免三个全量同步任务同时冲进新集群,导致新集群的内存和带宽被同时打满。

全量同步阶段会持续多久?我这边的数据量大概5GB左右(800万key),三个任务并行,同步真实耗时大约10分钟。同步期间我盯着Redis的master_repl_offset和redis-shake日志里的sync进度数据,确认三个源master的RDB快照都已经传输完成。

3.3 增量追平:如何判断可以切换

全量同步完成后,redis-shake会进入持续增量同步的状态,此时判断“能不能切”的核心指标是增量延迟。

在旧集群每个master上执行:

redis-cli -h 192.168.5.21 -p 6379 -a old_password INFO replication

看slave_repl_offset或者直接在redis-shake日志里看rdb和offset的差距。我当时定的标准是:增量延迟持续稳定在10秒以内才允许执行切换,而且要在切换前连续观察5分钟。

为什么会盯这个指标?因为切换动作本身要花费一定时间(改客户端配置、生效、重启连接池),如果增量延迟已经数十秒甚至更大,你切换期间新写入的数据其实还没到新集群,一切过去就丢数据。

增量的吞吐在低峰期基本追平,我观察到的延迟范围在200ms到3秒之间浮动,符合切换条件。

3.4 一致性校验的三层方法

增量追平后,别急着切换。我在切换前做了一轮比较严格的数据一致性校验,分三层:

第一层:对比key数量

redis-cli -h 192.168.5.21 -p 6379 -a old_password DBSIZE redis-cli -h 10.0.8.11 -p 6379 -a new_password DBSIZE

这里要注意,Cluster模式dbsize只统计单个节点,所以三个master要分别对比。差异在100个key以内且主要是过期key的抖动,属于正常范围。

第二层:抽样对比value

我写了一个小脚本随机抽1万个key,分别从新旧集群取value做hash对比。抽样范围我刻意覆盖了每个slot的key。这里推荐你用redis-cli的--bigkeys先梳理一下key的分布,然后每个节点取top关键字段抽样,比纯随机更有效。

第三层:检查TTL偏差

Redis的key迁移后,TTL字段必须保持和源key大致一致。redis-shake在同步时会把剩余TTL一起迁移,但增量同步中客户端如果对某个key执行了EXPIRE,这个更新也会同步过去。我抽了1000个带TTL的key对比,偏差都在1秒以内,符合预期。

校验通过后,我才开始设计切换流程。

4. 切换窗口的设计:从“准无缝”到“真无缝”

4.1 双写方案:真无缝的成本与收益

严格来说,“准无缝”方案在切换瞬间还是需要业务做一次极短时间的停止写入(秒级)。如果业务对“零写停止”有硬性要求,那就得上双写方案:在应用层把每次写操作同时发到旧集群和新集群,等增量追平,然后逐步把旧集群剥离。

双写方案的收益是显而易见的:写流量始终在新集群上打着,切换瞬间完全没有写空洞。但成本也高得多:

  • 应用层需要改数据访问层代码,做双写逻辑,这是一个上线后需要长期维护的额外复杂度。
  • 双写期间,每一次写操作最慢的那个集群决定链路时延,如果新集群比旧集群慢几毫秒,整体RT就会被拉高。
  • 双写期间的分布式锁、事务、Lua脚本这类Redis特性,实现起来非常痛苦。

我这边业务对写入连续性的容忍度还好(自动化任务在切换前做了暂停,核心交易链路在低峰期本身写量不大),所以我选择的是“增量追平+短窗口暂停写”的准无缝方案。如果你需要的是“连日志里都看不出切换痕迹”,那还是老老实实上双写。

4.2 先切读后切写:风险最小化的切换顺序

我的切换动作分成了三步,严格按“读→写→全量”的顺序来:

第一步,切读流量。把读请求通过配置中心(我这里用的是Nacos)动态切到新集群,观察新集群的读RT、命中率、错误率。因为Redis Cluster的读请求在master上处理,新集群的数据已经和旧集群一致了,读切换几乎是零风险的。

第二步,切写流量。写流量切换前,我先在旧集群上执行CLUSTER READONLY?不对,写切换的关键是让应用连接串指向新集群地址。这里我分了两批:先切20%的写流量,观察5分钟;再切剩余80%。整个过程没有遇到报错。

第三步,停增量同步。确认新集群数据稳定后,停掉redis-shake的增量同步。因为此时所有读写都已经指向新集群,旧集群已经没有新数据产生了,增量同步留着反而可能引入异常。

4.3 客户端重连与集群拓扑感知问题

这是整个切换过程中最容易翻车的点,单独拿出来说。

Redis Cluster的客户端(尤其是Jedis和Lettuce)启动时会通过CLUSTER SLOTS或CLUSTER NODES获取集群拓扑信息。如果你的应用连的旧集群地址,切走业务流量后,新集群虽然数据一样,但客户端本地缓存的节点映射表还是旧集群的IP端口。

之前操作的时候,我直接把应用配置里的Redis地址改成新集群地址,然后重启应用。结果发现有一部分应用实例在切换后的一段时间内,还在往旧集群节点发送请求(连接池里还保持着旧连接),导致出现了大量连接超时。

解决办法是切换前、切换中、切换后各做一次确认:

  • 切换前:确认应用侧Redis客户端版本支持动态刷新拓扑(Lettuce的topologyRefresh配置,或Jedis的cluster模式自动更新),如果不支持,切换后必须重启应用。
  • 切换中:在配置中心改地址后,旧的连接池会慢慢被淘汰,但你可以提前把连接池的空闲超时缩短,加快旧连接回收。
  • 切换后:观察监控里是否还有到旧集群IP的请求。我这边切换完成后大概持续了30秒,还有零星请求打在旧集群上,30秒后全部消失。

4.4 流量切换后的三大验证点

切片切完后不能直接宣布成功,我按照以下三个维度做了验证:

  • 业务指标:核心接口的P99、平均RT、错误率、Redis操作耗时,对比切换前后各一小时的数据。如果有明显劣化,立即回滚。
  • 数据写读验证:起一个临时脚本对新集群做一轮“写-读-对比”测试,确认新集群写入读取全链路OK。
  • 集群状态:确认CLUSTER INFO返回的cluster_state:ok,三个master的slot全部正常分配,从节点都在connected状态。

5. 迁移中踩过的坑复盘

5.1 版本差异导致的RDB同步失败

第一次跑redis-shake全量同步时,任务启动几秒就报错,日志里指向RDB解析失败。排查后发现旧集群RDB里有一个PROTO_VERSION字段跟新版本解析器不兼容的位,具体来说是旧RDB中某个内部编码的字符串类型,在新版本RDB解析逻辑里被当作未知类型处理了。

解决方式是升级redis-shake到最新版本,它内部会做RDB格式的兼容性处理(把旧版RDB解析成命令再逐条写入目标端),而不是直接搬运RDB文件。这里特别提醒:如果新旧集群的Redis版本差距大于一个大版本,优先升级redis-shake和redis-cli到最新版,而不是降级你们的生产Redis。

5.2 大key拖垮增量同步延迟

全量同步完成后,增量同步阶段我观察到延迟突然从秒级升高到分钟级。查redis-shake日志,发现有一个大list key一直在被业务高频地push数据,单条增量命令处理时间特别长。

这个问题的根因是:redis-shake的增量同步是逐命令回放的,RPUSH一次推几千个元素,处理这条命令就要花很长时间。我的缓解措施是让业务方临时调整了这个key的写入频率(把一批几千个元素拆成多次小批量写入),延迟立刻降回秒级。如果这种大key在你们业务里无法调整写入模式,可以考虑在切换前对这类key加白名单,先不完全同步,等切换完成后做一次补偿同步。

5.3 过期键与淘汰策略的玄学

迁移后我发现一个新集群某个master的expired_keys指标涨得很快,一开始以为是redis-shake同步过程导致的bug,后来排查发现:新集群的maxmemory-policy默认是noeviction,而旧集群配置的是allkeys-lru。迁移后所有key带着各自的TTL进了新集群,但内存淘汰策略不一样,导致新集群在接近内存上限时,某些在旧集群里本来会被LRU淘汰掉的key,在新集群没有配置淘汰策略的情况下只能靠空间硬扛。

这个坑的教训是:集群迁移不只是迁数据,Redis服务端配置也是“数据”的一部分。新集群所有跟内存、持久化、淘汰相关的配置,必须和旧集群对齐后再开启流量。

5.4 回滚方案里最容易被忽略的连接池问题

我把回滚预案写好后,模拟了一次“切换后3分钟内发现问题并回滚”的演练。回滚到旧集群地址时,发现有几个应用实例的连接池已经彻底切到新集群了,改回配置后没有触发连接重建,还是连着新集群。

后来在应用的Redis客户端配置里增加了连接池重启的接口调用,回滚时通过运维平台的发命令通道强制所有实例刷新连接池,才算彻底解决。这里实际的经验是:回滚方案必须包含“强制客户端刷新连接”这一步,不然你改了配置中心也没用。

6. 收尾不止“下线旧集群”:验证与监控重建

6.1 下线前必须确认的三件事

切换到新集群稳定运行三天后,才开始考虑旧集群下线。下线前我确认了三件事:

  • 旧集群确实没有读流量和写流量了。这个不但要看应用监控,还要在旧集群上用MONITOR命令蹲10分钟,确认没有任何业务命令进来。
  • redis-shake的所有同步进程已经停止,避免进程残留对旧集群做多余操作。
  • 旧集群的持久化数据做了一次完整备份(RDB + AOF手动触发),存到冷备盘。虽然大概率用不上,但万一新集群出问题,至少还能把旧集群重新拉起来。

6.2 监控指标对比与基线修正

迁移完成后,我把新集群的监控拉出来和旧集群的历史数据做了一次基线对比。重点看三类指标:

  • 慢查询日志:新集群的slowlog有没有出现旧集群没出现过的慢命令。
  • 内存碎片率和redis内存占用:容器化部署带来的内存分配行为变化,会影响mem_fragmentation_ratio,需要重新调maxmemory和碎片整理参数。
  • 网络流量和连接数:容器网络和物理网卡的吞吐上限不同,要把告警阈值调低一些,避免真正出问题时没触发告警。

这三个指标让我发现了新集群一个比较严重的问题:连接数比旧集群高出40%,原因是容器平台默认给Redis实例配置了更短的空闲连接超时,导致应用反复重连。最后我在容器侧调大了tcp-keepalive和timeout参数,连接数才回归正常。

写在最后的经验

这次迁移完成之后,一个很直观的收获是:三节点Redis集群看起来规模不大,但迁移方案设计的复杂度一点不比大规模集群低。所有方案都要围绕“数据不丢、业务不停”这两个目标来选,而不是哪个工具名气大用哪个。redis-shake这套路线,在我当时的条件下是最稳妥的。

如果你下次也要做类似迁移,我建议按这个顺序来走:先理清新旧集群的版本和配置差异,再定同步工具和同步模式,切换前把校验脚本先跑起来,最后把回滚方案当成主方案的一部分来准备。尤其是回滚方案,很多迁移事故不是迁移过程翻车,而是出了问题上不去旧集群,最后变成两边都不落的僵局。希望大家都能平稳完成迁移,少踩我踩过的这些坑。

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

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

立即咨询