跳到主内容

从 8 秒到 50ms:Redis 主从延迟排查实录 ​

问题背景:读分离为什么会读到旧数据 ​

一次大促压测期间,业务在主节点写入订单状态后,立即从只读副本读取,偶发拿到旧值。监控显示主节点 QPS 并不高,但从节点的复制延迟最高冲到 8 秒,随后还出现多次 Full resync,副本短时间只读旧数据。

这类问题通常不是单一参数造成的,而是以下因素叠加:

  • 主节点写入命令过大,例如一次写入几百 MB 的大 Key;
  • repl-backlog 太小,副本短暂断连后无法增量同步;
  • 主节点执行 RDB 慢、网络抖动或从节点磁盘 IO 紧张;
  • 读请求被强路由到延迟副本,业务又把主从复制当成强一致。

Redis 主从复制默认是异步复制:主节点执行写命令后,不等待所有副本确认就向客户端返回成功。因此,读分离只能提升吞吐,不能天然保证读写一致。

主从复制链路:增量复制为什么依赖 backlog ​

Redis 副本连接主节点时,会使用 PSYNC 机制携带复制 ID 和偏移量。主节点如果在复制积压缓冲区中还能找到该偏移量之后的数据,就执行增量复制;找不到则只能全量同步。

客户端写入SET / EVAL主节点执行更新本地数据repl-backlog环形复制缓冲区复制偏移量PSYNC 续传只读副本异步追平

repl-backlog 是一个环形缓冲区,只保存最近传播给副本的写命令。它的容量决定了副本最多能容忍多长时间的断连。若主节点每秒写入 20MB,backlog 只有 64MB,副本断连超过 3 秒左右,旧偏移量就可能被覆盖。

方案对比:先止血还是直接扩容 ​

常见治理方案有三类:

方案适用场景优点风险
调大 repl-backlog-size网络短暂抖动、副本偶发重启降低全量同步概率占用更多内存
控制大 Key 与批量写入主节点复制输出缓冲持续暴涨从根因降低复制压力需要改造业务写入方式
关键读走主库或一致性校验下单、支付、权限变更业务结果确定牺牲部分读扩展能力

不要把调大 backlog 当成唯一手段。如果主节点持续产生巨型命令,或者从节点机器资源已经耗尽,再大的缓冲区也只能延缓问题爆发。

实操步骤一:确认延迟发生在哪里 ​

先分别查看主从复制状态。以下命令可直接在 redis-cli 中执行:

bash
# 进入主节点,查看副本列表、同步状态、延迟
redis-cli -h 127.0.0.1 -p 6379 INFO replication

# 查看每个副本的详细信息,重点关注 offset、lag、state
redis-cli -h 127.0.0.1 -p 6379 CLIENT LIST TYPE replica

# 进入从节点,确认它认为的主库偏移量和自身已处理偏移量
redis-cli -h 127.0.0.1 -p 6380 INFO replication

重点字段含义如下:

  • master_link_status:副本与主库连接是否正常;
  • master_repl_offset:主库当前复制偏移量;
  • slave_repl_offset:副本已复制到的偏移量;
  • lag:副本最近一次确认距离当前的秒数;
  • master_sync_in_progress:是否正在全量同步;
  • sync_full:历史上触发全量同步的次数。

可以用下面的命令持续观察主从偏移量差距:

bash
# 每 1 秒输出一次复制信息,观察 offset 是否持续拉大
watch -n 1 \
  'redis-cli -p 6379 INFO replication | grep -E "repl_backlog|connected_slaves"; echo ---; redis-cli -p 6380 INFO replication | grep -E "master_link_status|master_sync|slave_repl_offset"'

如果 sync_full 不断增加,说明副本反复全量同步;如果偏移量差距不断扩大但没有全量同步,说明从节点应用命令、网络传输或主节点生成 RDB 的能力跟不上写入速度。

实操步骤二:合理调整 repl-backlog ​

查看当前 backlog 配置与运行状态:

bash
# 查看 backlog 容量、是否启用、首字节偏移量和历史长度
redis-cli -p 6379 CONFIG GET repl-backlog-size
redis-cli -p 6379 CONFIG GET repl-backlog-ttl
redis-cli -p 6379 INFO replication | grep repl_backlog

在线调整可以先止血,再写入配置文件持久化:

bash
# 运行时调整为 512MB,单位是字节
redis-cli -p 6379 CONFIG SET repl-backlog-size 536870912

# 没有副本时保留 backlog 1 小时,便于副本短时间回归后续传
redis-cli -p 6379 CONFIG SET repl-backlog-ttl 3600

# 持久化到 Redis 配置文件,具体路径以实际部署为准
redis-cli -p 6379 CONFIG REWRITE

容量评估不要只凭经验。可以按以下公式估算:

text
建议 backlog = 主节点平均每秒复制写入字节数 × 可容忍断连秒数 × 2

例如主库每秒复制输出 15MB,希望容忍 30 秒网络抖动,理论值为 450MB,考虑写入尖峰可配置为 1GB。注意 backlog 是主节点上的复制积压缓冲,不是从节点越多就线性放大;但多个副本仍可能各自占用输出缓冲区。

实操步骤三:用关键数据结构复现问题 ​

下面使用 Hash 演示正常写入和大 Key 的差异。小字段增量更新通常比一次性大对象覆盖更友好:

bash
# 小步更新订单 Hash,主库执行后会异步传播到从库
HSET order:1001 status PAID amount 9900 user u_88
HSET order:1001 status SHIPPED express sf_123456

# 在从库读取,验证异步复制窗口内可能读到旧值
HGETALL order:1001
HGET order:1001 status

危险写法是在一个 Hash、List 或 String 中塞入超大对象。例如:

bash
# 不推荐:一次生成几十万元素,单个命令会形成巨大复制压力
# for ((i=0;i<100000;i++)); do redis-cli HSET big:order $i "$RANDOM"; done

# 更推荐:按业务分片,控制单个 Key 的元素数量与单次写入体积
HSET order:1001:items:1 sku_1 count 2 price 19900
HSET order:1001:items:2 sku_2 count 1 price 5900

# 检查大 Key,生产环境建议在从库或低峰期执行
redis-cli --bigkeys --i 0.1

批量写入也应分批并限速。Pipeline 能降低 RTT,但一次打包几十 MB 命令会让主节点、复制缓冲区和从节点同时承压。

实操步骤四:缓解读写不一致 ​

对于必须立即读到自己写入结果的请求,可以在写后使用 WAIT 做有限等待:

bash
# 写入订单状态
SET order:1001:status PAID

# 等待至少 1 个副本确认复制,最多等待 200ms
WAIT 1 200

# 再从副本读取;若 WAIT 返回 0,说明没有副本在超时时间内确认
GET order:1001:status

WAIT 只能提高读到新数据的概率,它不等价于强一致事务。发生主从切换时,已确认但未成为新主库的副本仍可能不被选中。因此关键业务更稳妥的策略是:

  1. 支付、扣款、权限变更等强一致读走主库;
  2. 普通详情、列表、报表走从库;
  3. 写后短时间内携带“读主”标记,例如 1 秒内的订单详情强制走主;
  4. 代理层剔除高延迟副本,超过阈值就临时摘流。

避坑总结:三个高频故障点 ​

1. backlog 调大后仍然反复全量同步 ​

常见现象是日志频繁出现 Full resync from master,sync_full 持续增长。很多同学只看到 backlog 太小,却忽略了副本重启后 runid 变化、复制 ID 不匹配,或者主节点发生故障切换形成新复制历史。修正方法是同时检查 master_replid、master_replid2、副本偏移量与重启记录;不要只机械增加内存参数。

2. 在主库高峰期执行 KEYS * 或超大 Lua 脚本 ​

现象是主节点 CPU 飙高,从库延迟同步上升,业务读到旧数据。原因是 Redis 命令按顺序执行和传播,慢命令会阻塞复制数据发送与从库命令应用。应改用 SCAN,并保证 Lua 脚本短小、确定、不依赖随机结果,避免在脚本中循环处理海量 Key。

3. 把从库延迟只归因于网络 ​

实际排查中,从库磁盘 IO 不足、RDB 落盘慢、开启慢盘 AOF appendfsync always,都可能让副本应用速度下降。应同时观察主节点 client-output-buffer-limit replica、从节点 rdb_bgsave_in_progress、CPU、网络和磁盘利用率。若复制输出缓冲超过限制,主库还可能主动断开副本,再次诱发全量同步。

4. 业务无差别读从库 ​

典型错误是“写主库后立刻读从库”,并把偶发旧数据当成缓存问题。修正方式是按一致性等级路由:强一致读主,最终一致读从;写后短暂读主;对副本延迟设置熔断阈值。读分离的核心前提,是业务能够接受复制窗口内的旧值。

最终,本次案例将 repl-backlog-size 从默认 64MB 提升到 1GB,拆分超大 Hash,限制单批 Pipeline 命令体积,并对订单写后 1 秒内请求强制读主。调整后副本常规延迟稳定在 50ms 以内,网络抖动 20 秒内不再触发全量同步,业务侧旧读投诉也随之消失。

免责声明

本工具与文章内容仅用于学习测试场景,优化建议仅供参考,请勿直接用于生产环境,由此产生的风险自行承担。