外观
从 8 秒到 50ms:Redis 主从延迟排查实录
问题背景:读分离为什么会读到旧数据
一次大促压测期间,业务在主节点写入订单状态后,立即从只读副本读取,偶发拿到旧值。监控显示主节点 QPS 并不高,但从节点的复制延迟最高冲到 8 秒,随后还出现多次 Full resync,副本短时间只读旧数据。
这类问题通常不是单一参数造成的,而是以下因素叠加:
- 主节点写入命令过大,例如一次写入几百 MB 的大 Key;
repl-backlog太小,副本短暂断连后无法增量同步;- 主节点执行 RDB 慢、网络抖动或从节点磁盘 IO 紧张;
- 读请求被强路由到延迟副本,业务又把主从复制当成强一致。
Redis 主从复制默认是异步复制:主节点执行写命令后,不等待所有副本确认就向客户端返回成功。因此,读分离只能提升吞吐,不能天然保证读写一致。
主从复制链路:增量复制为什么依赖 backlog
Redis 副本连接主节点时,会使用 PSYNC 机制携带复制 ID 和偏移量。主节点如果在复制积压缓冲区中还能找到该偏移量之后的数据,就执行增量复制;找不到则只能全量同步。
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:statusWAIT 只能提高读到新数据的概率,它不等价于强一致事务。发生主从切换时,已确认但未成为新主库的副本仍可能不被选中。因此关键业务更稳妥的策略是:
- 支付、扣款、权限变更等强一致读走主库;
- 普通详情、列表、报表走从库;
- 写后短时间内携带“读主”标记,例如 1 秒内的订单详情强制走主;
- 代理层剔除高延迟副本,超过阈值就临时摘流。
避坑总结:三个高频故障点
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 秒内不再触发全量同步,业务侧旧读投诉也随之消失。
免责声明
本工具与文章内容仅用于学习测试场景,优化建议仅供参考,请勿直接用于生产环境,由此产生的风险自行承担。