首页 / 资讯中心 / 文章详情

Redis主从复制网络抖动排查与参数调优实战指南

Redis主从复制网络抖动排查与参数调优实战指南 ★ FEATURED ARTICLE
1. 先看故障现场一次网络抖动引发的复制雪崩1.1 凌晨3点的告警与三类并发异常先还原一个真实场景。某个凌晨3点监控平台突然弹出告警Redis主从复制节点出现断连随后短短几分钟内同一机房的多组主从集群先后报出同样的错误。登录从节点查看日志满屏都是MASTER - REPLICA sync started和MASTER - REPLICA sync failed交替出现。主节点那边则是Unable to fsync to replica一类的报错日志时间点和网络设备的丢包告警完全对上——典型的网络抖动触发的连锁反应。这种故障最麻烦的地方在于它不是一次简单的断连而是断连-重连-再断连的循环。每次重连都伴随握手、身份认证、偏移量对齐如果 backlog 不够还会直接退化成全量同步。全量同步在主节点内存几个 GB 甚至几十 GB 的场景下要 Fork 子进程生成 RDB要传输大量数据短时间内把主节点的 CPU、带宽、磁盘 IO 全部拉满反而进一步恶化了网络状况形成恶性循环。很多同学遇到这种情况第一反应是网络问题等恢复了就好实际上网络恢复之后复制风暴引发的延迟和负载还在持续。1.2 短时间内大量断连为什么危险这里要澄清一个概念网络抖动本身不可怕可怕的是抖动之后的重建过程。Redis 的主从复制基于 TCP 长连接主节点会持续向从节点推送写命令。一旦网络出现几秒到几十秒的丢包或延迟飙升TCP 层会触发重传重传超时RTO如果超过 Redis 的repl-timeout判定窗口主节点就会认为从节点已经失联主动断开连接。断开之后从节点会按照repl-ping-replica-period的周期默认 10 秒重新发起连接。如果网络还没完全恢复重连握手可能再次超时于是又断开又重连。而每次重连都要重新协商复制偏移量如果断连期间产生的数据量已经超出了repl-backlog-size的容量从节点就只能请求全量同步。全量同步过程中如果再次断连下一次还得重新全量。我在生产环境见过最极端的情况一个 4GB 内存的从节点一个晚上触发了 12 次全量同步每次跑到一半就断主节点被迫反复 Fork 生成 RDB最终把主节点拖到 OOM。所以做稳定性调优的核心思路就两条一是让断连判定更合理别因为短暂抖动就轻易判死二是让重建过程更轻量即使断了也能走增量同步而不是全量同步。这两条主线贯穿了后面所有的参数调整和架构改动。2. 断连判定机制拆解超时、心跳与PSYNC的代价2.1 主从复制的基础链路回顾先把主从复制的工作机制简单过一遍。Redis 主从复制分为两个阶段。第一阶段是握手建连。从节点向主节点发起连接主节点回以PONG然后双方交换端口信息如果是新版 Redis 且有配置密码还要进行AUTH认证。握手完成之后主节点开始向从节点传输 RDB 快照这个阶段叫全量同步。第二阶段是增量同步。RDB 传输完成后主节点会把传输期间缓存在 backlog 中的写命令继续推给从节点从节点开始以近乎实时的方式接收主节点推送的命令流。为了保证连接是活着的从节点每隔repl-ping-replica-period默认 10 秒会向主节点发送一个 PING 心跳主节点和从节点都会维护一个最后一次收到对方消息的时间点只要这个时间戳距今超过repl-timeout默认 60 秒就会判定对方失联关闭连接。注意这里的repl-timeout判定是双向的主节点会判定从节点从节点也会判定主节点。一旦任何一方判定超时连接都会被关掉。这个细节很重要因为主节点判死和从节点判死的触发条件稍有不同后面调参时才能对症下药。2.2 超时定时的竞态为什么60秒还不够很多人以为repl-timeout默认 60 秒网络抖动一般也就几秒几十秒怎么都不至于触发断连。但问题在于这个超时是累计判定的主节点从收到从节点最后一个 PING 或命令开始计时如果在这段时间内一直没收到任何有效数据包括 TCP 层面的零窗口信号那么 60 秒一到就断。而网络拥塞时TCP 重传的退避时间是指数增长的第一次重传等 1 秒、第二次 2 秒、第三次 4 秒……如果丢包率较高一个包可能要等十几二十秒才能重传成功中间再有几次连续丢包60 秒的窗口很容易被消耗殆尽。更隐蔽的是从节点的 PING 是每 10 秒发一次的。如果恰好 PING 发出的时刻赶上一波网络抖动这个 PING 丢了TCP 重传没赶上主节点那边的计时器已经走了 10 秒再之后如果主节点推送写命令也遇到丢包和重传两边累计下来很容易突破 60 秒。所以在网络抖动频繁的环境里60 秒的默认值实际上不够稳健。把它适当调大比如调到 120 秒甚至 180 秒能给 TCP 重传留出充裕的恢复窗口让短暂的抖动熬过去而不是一抖就断。有人担心调大之后主节点判定失联变慢影响故障切换速度。这个担心有道理但要分场景主节点和从节点之间如果真有长时间网络分区Sentinel 或 Cluster 的故障切换并不依赖repl-timeout而是靠自身的down-after-milliseconds判定所以调大repl-timeout只会减少假断连不会拖慢真故障的切换。2.3 增量复制还是全量复制分水岭就在backlog如果断连时间不长从节点重连时可以发送自己当前的复制偏移量主节点检查 backlog 里是否还留有这个偏移量之后的数据。如果有就从该偏移量开始继续推送这就是增量复制PSYNC 2。如果偏移量已经不在 backlog 范围内主节点只能做全量同步。所以 backlog 的大小直接决定了可以容忍多长的断连时间。计算公式很简单可容忍断连时间 repl-backlog-size / 每秒写入字节数比如主节点每秒写入约 500KB 的数据backlog 默认 1MB那最多只能容忍约 2 秒的断连。网络抖动稍微持续几秒offset 就被顶出 backlog重连后必然全量同步。要容忍 30 秒的抖动backlog 至少需要 15MB生产环境我一般建议设置成峰值写入速率 × 预期最长抖动时间 × 2 倍冗余同时设置repl-backlog-ttl为 3600 秒以上确保在复制稳定期间 backlog 不会被过早释放。这里还要普及一个关键背景Redis 4.0 之后引入的 PSYNC2让从节点在主节点侧发生故障切换failover之后也能尝试增量同步而不是像 PSYNC1 那样必须全量。但 PSYNC2 依然依赖 backlog如果断连时间太长导致 backlog 过期照样退化为全量。所以调大 backlog 对于网络抖动场景是性价比最高的一步改动。3. 可落地的参数调优组合一组经过验证的推荐配置3.1 repl-timeout 的调整逻辑与计算在 Redis 的 redis.conf 里和复制断连直接相关的参数有这么几个repl-ping-replica-period从节点发送 PING 的周期默认 10 秒。repl-timeout超时判定窗口默认 60 秒。repl-backlog-size复制积压缓冲区大小默认 1MB。repl-backlog-ttlbacklog 在所有从节点断开后的存活时间默认 3600 秒。tcp-keepaliveTCP 层保活探测周期默认 300 秒。min-replicas-to-write和min-replicas-max-lag从节点数量下限和最大延迟用于保护一致性。先说repl-timeout。我给出一组在抖动网络下经过验证的推荐配置repl-ping-replica-period 10 repl-timeout 120 repl-backlog-size 64mb repl-backlog-ttl 7200repl-timeout为什么是 120 而不是 60我们这么算一个 PING 周期 10 秒加上 TCP 重传在抖动网络下可能产生的 30~60 秒延迟再留出至少 2~3 个重传周期作为缓冲60 秒刚好踩在边界上一有风吹草动就断了。120 秒则意味着即使一次心跳丢失且后续一条写命令在网络上挣扎了四五十秒还没送达两边仍不会立刻判死给了网络自愈足够的时间窗口。但也有反向调整的场景。如果你的业务对主从切换的时效性要求非常高不能接受 2 分钟的无主时段那就要配合 Sentinel 的down-after-milliseconds一起评估。我见过一个折中方案repl-timeout调到 90 秒Sentinel 那边down-after-milliseconds调到 20000故障切换反而比之前更稳——因为假断连少了真正触发切换的次数也少了。3.2 repl-backlog-size 的计算方法backlog 的计算我在第 2 节已经给出公式这里再具体化一下。先用INFO replication看主节点的master_repl_offset隔 60 秒再看一次差值就是每秒写入字节数的近似值。也可以用redis-cli --stat观察instantaneous_ops_per_sec把每条命令的平均字节数算进去。生产环境我通常直接给 64MB 起步因为内存成本很低但全量同步的代价极高——一次全量同步除了浪费带宽还要主节点 Fork 子进程内存翻倍风险、磁盘 IO 压力都在这里。用 64MB 内存换断连 1 分钟以内都能增量续传的冗余这笔账太划算了。再补一个实操细节修改 backlog 后必须在所有从节点上执行REPLICAOF NO ONE再REPLICAOF master port重新建立复制否则旧 master 上的 backlog 配置不会生效。严格来说配置是写在主节点上的但从节点重连时主节点才按自己的配置创建 backlog所以只需要在主节点改配置后重启或 CONFIG SET 动态调整然后触发一轮全量同步即可让新 backlog 生效。3.3 tcp-keepalive 与 min-replicas 的组合思路tcp-keepalive默认 300 秒意思是 Redis 会在建立了连接但空闲 300 秒时发送 TCP 保活探测包。对于复制场景主从之间通常都有持续的命令流或心跳所以 keepalive 平时用不太上。但网络抖动时TCP 层的 keepalive 探测可以帮助内核更早发现连接已死从而触发快速重连。建议把tcp-keepalive调到 60 秒让内核在 TCP 层面更积极地进行状态检测。配合系统中net.ipv4.tcp_keepalive_probes参数可以做到两分钟内判定死连接。min-replicas-to-write和min-replicas-max-lag不是直接治断连的而是治网断了但数据照写的。生产环境一般配置为min-replicas-to-write 1 min-replicas-max-lag 10意思是如果当前可用从节点少于 1 个或者某个从节点的延迟超过 10 秒主节点就拒绝写入。这样做的好处是如果网络抖动已经严重到所有从节点都掉线了主节点直接对外返回写入失败让上层业务感知到风险而不是继续写入制造更大的 offset 鸿沟等从节点恢复后不得不做全量同步。对于一个从节点的场景min-replicas-to-write 1可能会让主节点在从节点抖动时拒绝所有写入这一点需要业务侧评估。如果业务不能接受写失败就把这个值设为 0只保留min-replicas-max-lag做告警。3.4 从节点侧两个容易忽略的配置从节点侧有两个配置经常被忽略但在抖动场景下影响很大。第一个是replica-serve-stale-data yes/no。当从节点与主节点断连后从节点仍然能响应读请求但数据可能已经过期。yes是继续返回可能过期的数据no是返回错误。对一致性要求高的业务应该设成no让读流量不要打到已断连的从节点上避免读到脏数据。对可用性要求高的业务则设成yes保证读服务不中断。第二个是replica-read-only yes。在生产环境务必要保证从节点是只读的否则一旦断连期间有写入从节点重连后可能出现数据冲突或复制中断。这个参数默认就是yes但有些同学在排查问题时手动对从节点写过数据却忘了改回来这会引发非常诡异的复制问题——从节点带着额外的 key 去对齐主节点偏移量主节点发现差异后会拒绝继续增量同步。这两个参数配合上主节点侧的min-replicas限制可以形成一个完整的数据一致性保护链路从节点断连时要么拒绝读、要么标记数据过期主节点断连时要么拒绝写、要么告警让业务和数据层都有明确的状态感知。4. 操作系统层TCP保活、内核参数与网卡层面的配合4.1 内核 TCP 参数让链路状态检测更灵敏Redis 参数调好了断连判定这一侧的人为因素就基本解决了。但 TCP 层的反应速度同样决定了一次抖动的走向。如果 TCP 层要等很久才能发现链路死了Redis 层即使repl-timeout设得再大连接状态也不健康。反之如果 TCP 层能快速探测、快速重连Redis 层的复制链路也能更快恢复。Linux 内核中和 TCP keepalive 相关的三个参数net.ipv4.tcp_keepalive_time 30 net.ipv4.tcp_keepalive_intvl 10 net.ipv4.tcp_keepalive_probes 3tcp_keepalive_time是空闲多久开始探测tcp_keepalive_intvl是每次探测的间隔tcp_keepalive_probes是连续几次失败判定连接断开。上面这组值的含义是TCP 连接空闲 30 秒后开始探测每 10 秒探一次连续 3 次无响应就判定死链。这样最快 30 3×10 60 秒就能发现死链比默认的 2 小时 11 分钟快了太多。对 Redis 这种对延迟敏感的服务这个参数能显著缩短网络已经断了但应用还没感知的时间窗口。另一个重要参数是net.ipv4.tcp_retries2。它控制 TCP 在认定连接不可用之前要重传的次数默认 15 次在极端抖动下可能持续重传 15~30 分钟才放弃。对于 Redis这个值调小一点比如 5~8 次可以让内核更快放弃死链、释放资源触发新的连接建立。注意区分repl-timeout是让 Redis 主动算超时tcp_retries2是让内核在 TCP 层早点认输。两者协同Redis 层能在 120 秒内判死TCP 层也能在差不多的时间窗口内配合不会出现 Redis 等了 120 秒、内核还在死命重传的错位。4.2 网卡与中断绑核隐藏在高延迟背后的性能瓶颈网络抖动有时候不完全是网络的问题而是主机侧的软中断处理不过来。Redis 本身是单线程事件循环但它依赖内核网络栈收包。如果网卡中断都打在一个 CPU 核上其他核在忙收包核在积压就会出现纳秒级到毫秒级不等的延迟波动表现上跟网络抖动一模一样。排查方法是看/proc/interrupts里网卡中断的分布。如果多队列网卡支持可以开启 RSSReceive Side Scaling把中断分散到多个核上配合irqbalance服务让中断自动均衡。此外如果 Redis 实例绑核了比如通过taskset或numactl固定到特定核要确保网卡中断核心和 Redis 运行核心不重叠避免争抢 CPU。还有网卡的中断合并ethtool -C eth0 rx-usecs控制合并延迟默认可能是几十微秒。对延迟极敏感的 Redis可以把rx-usecs调小比如 8~16 微秒降低收包延迟。这个操作要谨慎调太小会增加 CPU 开销建议先在测试环境对比观察。4.3 交换机与链路层面的冗余设计再往外一层是物理链路。单条千兆链路从接入交换机到 Redis 主机如果这中间存在共享链路上的突发流量Redis 主从的延迟就会周期性飙升。生产环境建议主从之间走独立的 VLAN避免和业务流量混跑。网卡 bonding用mode 4LACP对接交换机做链路聚合这样单条链路故障可以平滑切换。如果条件允许主节点和从节点各接两块网卡分别接到不同的交换机避免单交换机故障导致所有节点同时失联。这几个操作看上去是运维的基础课但却是很多 Redis 断连问题的终极答案。我遇到过不止一次Redis 参数怎么调都还是抖最后查出是同交换机下的一个大数据任务在凌晨定时跑全量导出把交换机端口打得拥塞。把复制流量隔离到独立链路之后问题立刻消失。5. 部署架构层面的稳定性兜底再多一层保险5.1 同机房部署与跨机架感知网络抖动场景下最直接有效的架构改动是不要把主从放在同一台物理机也不要跨机房长链路互联。同一机房的单程 RTT 通常低于 1 毫秒加入抖动模拟机也能控制在 10 毫秒以内而跨机房的网络抖动动辄几十毫秒起步甚至出现分钟级的黑洞。主从复制链路是长连接长连接在长距离链路上受到 TCP 拥塞窗口、RTO 退避、中间设备状态检测的影响更大所以同机房部署永远是第一选择。如果业务确实需要跨机房容灾那就要在参数上留出更大的repl-timeout比如 300 秒同时把 backlog 按跨机房带宽的峰值写入量计算保证断连五分钟内可以增量续传。还要在 Sentinel 或 Cluster 的配置中增加cluster-replica-no-failover之类的开关避免跨机房间的网络抖动直接触发频繁的故障切换。跨机房场景下的调优目标不是不断连而是断连后不雪崩、切换后不振荡。5.2 多从节点与故障域分散单个从节点的方案在抖动环境下明显脆弱主节点只要判断这个唯一从节点失联复制链路就完全中断。生产环境建议至少保留两个从节点并且让它们分布在不同的机架上。这样即使一个从节点所在链路抖动另一个从节点还在主节点可以把复制链路切换到健康的从节点上比如通过REPLICAOF提升一个新主。多从节点还有一个好处:全量同步的负担被分摊。假设主节点每秒产生 500KB 写命令两个从节点各自维护自己的 backlog断连时各自尝试增量同步或全量同步不会全部压在同一时刻。配合client-output-buffer-limit replica参数给每个从节点设置合理的输出缓冲区上限可以防止一个从节点同步过慢导致主节点发送缓冲区暴增反过来影响主进程。5.3 客户端侧的读写分离容错最后是应用侧。很多项目用读写分离读走从节点写走主节点。网络抖动时从节点短暂断连如果客户端没有感知就会读到过期数据或连接错误。合理的做法是在客户端层面做一次熔断降级当从节点的读延迟超过阈值或连接错误达到一定比例时把读流量临时切到主节点。主节点多扛一部分读流量不是大问题但等从节点恢复后再切回去这样能避免应用层在抖动时被大量读错误淹没。这里给出一个小经验主从复制断连期间的读错误很多其实是连接被动断开后连接池里的旧连接还在被复用造成的。Redis 客户端比如 Jedis、Lettuce都有连接池断连发生时服务端主动关闭了连接但连接池里的连接对象不知道下次取用时才发现已经失效。这个问题的解法是让连接池在获取连接时做一次 validate发送 PING或者在服务端那边快速响应PONG。代价是每次取连接多一次 RTT但对正确的数据读取来说完全值得。6. 压测验证与监控告警把调优成果固化下来6.1 用延迟注入模拟抖动验证调优效果参数调好之后一定要做一次断连演练否则没法确认改动到底有没有用。现在多数云平台支持网络层故障注入也可以自己用tc命令实现# 模拟 30 秒额外延迟 丢包 10% tc qdisc add dev eth0 root netem delay 200ms loss 10% # 结束模拟 tc qdisc del dev eth0 root netem在注入抖动之前先记录主从复制当前的状态info replication中的master_repl_offset和slave_repl_offset以及延迟指标master_last_io_seconds_ago。然后注入抖动观察断连是否发生多久发生从节点重连后走的是partial resync还是full resync故障切换是否被误触发恢复后 offset 对齐时间是多少我实测的结果是调优前200ms 延迟 10% 丢包环境下主从几乎必然断连并触发全量调优后repl-timeout 120repl-backlog-size 64mb同样的注入条件下连接保持稳定偶尔断连也能在几十秒内完成增量重连。这个验证过程一定要落在文档里方便后续对比和回归。6.2 关键监控指标与告警阈值调优不是一锤子买卖后续要靠监控持续观测。复制的关键监控指标至少有五个指标获取方式正常范围告警建议master_last_io_seconds_agoINFO replication小于 10 秒大于等于 repl-timeout 的 50%slave_repl_offset 落后量INFO replication0 或极小落后超过 backlog 的 50%全量同步次数累计INFO stats低频短时间内全量次数超过 2 次断连重连次数INFO stats 的 sync_partial/sync_full稳定每分钟超过 3 次从节点 ping 延迟redis-cli --latency 或客户端毫秒级超过 50ms 持续 30 秒特别是sync_partial和sync_full两个计数可以在INFO stats里直接查到。如果sync_partial持续上升说明增量重连生效了如果sync_full频繁出现说明 backlog 还是不够或者断连时间太长需要继续调大repl-backlog-size或缩短抖动时长。6.3 复盘 Checklist每次断连故障都该查什么最后给一份排障 Checklist是从实际故障中总结的每次遇到 Redis 主从断连按这个顺序过一遍基本不会漏。第一步看时间线。主从断连的时间点是否和网络设备告警重合如果是铁定是网络抖动如果不是检查是否有人改了防火墙策略。第二步查INFO replication的master_repl_offset和slave_repl_offset差值确定断连窗口内产生了多少数据。第三步看sync_full计数确认断连后是增量还是全量。第四步查内核日志dmesg看有没有网卡 reset、软中断超时等异常。第五步查 Redis 日志中是否有Unable to fsync或Disk I/O Error排除磁盘本身的问题。第六步确认repl_backlog_size是否覆盖了断连窗口的数据量如果覆盖了还走全量说明是 PSYNC2 没有生效优先级最高的排查项是主从节点的 Redis 版本是否都在 4.0 以上。这个 Checklist 的价值在于它能帮你区分网络抖动是根因还是网络抖动只是表象——比如磁盘 IO 卡顿导致主进程无法及时回复从节点的 PING表现看起来也是断连实际根因在存储。把五类常见根因都过一遍才能避免每次抖动都只调网络侧参数忽视了真正的瓶颈。这套调优思路在我维护的多个 Redis 集群上都跑了一年多效果稳定。核心就一句话把repl-timeout放宽、把 backlog 调大、让 TCP 更快探活、让客户端更聪明地感知复制的真实状态。做到这四点网络抖动下的主从复制就不会再是半夜叫醒你的那个告警了。
阅读完成 · 觉得有帮助?
咨询建站