【Redis从入门到精通】第41篇:PSYNC登场——部分重同步如何拯救断线重连
2026/6/3 22:53:11 网站建设 项目流程

上一篇【第40篇】旧版复制的硬伤——Redis 2.8之前为什么会反复全量同步
下一篇【第42篇】 复制全流程——从slaveof到数据同步完成


上一篇文章我们扒了旧版SYNC的底裤——每次断线重连都要全量同步,哪怕只断开1秒,也要把整个RDB重新传一遍。这感觉就像你网购退货,商家让你把整栋仓库搬空再重新进货一样离谱。今天,PSYNC带着它的三大法宝登场了。

一、PSYNC协议:从"一刀切"到"精细化"

Redis 2.8引入了PSYNC(Partial Sync)协议,替代旧版SYNC。PSYNC的核心思想非常朴素:能不全量就不全量,只同步丢失的那部分数据

PSYNC有两种模式:

模式触发条件数据传输量
完整重同步(Full Resync)从库首次连接/无法部分重同步全量RDB + 增量命令
部分重同步(Partial Resync)短暂断线后重连,条件满足仅丢失期间的命令
┌─────────────────────────────────────────────────┐ │ PSYNC 协议 │ ├──────────────────┬──────────────────────────────┤ │ 完整重同步 │ 部分重同步 │ │ (Full Resync) │ (Partial Resync) │ │ │ │ │ · 首次复制 │ · 短暂断线重连 │ │ · runid不匹配 │ · runid匹配 │ │ · offset超出 │ · offset在积压缓冲区范围内 │ │ 积压缓冲区 │ │ │ │ │ │ 主库 → RDB → 从库│ 主库 → 积压缓冲区命令 → 从库 │ │ + 增量命令传播 │ │ └──────────────────┴──────────────────────────────┘

部分重同步的关键在于三个"法宝":复制偏移量复制积压缓冲区服务器运行ID。缺一不可,下面逐一拆解。

二、复制偏移量:主从的数据坐标

主库和从库各自维护一个复制偏移量(replication offset),它就像一条刻度尺上的游标,标记着"数据同步到了哪个位置"。

主库视角: master_repl_offset = 1024 │ ▼ [0.........512.........1024.........1536] ▲ │ 从库视角: │ slave_repl_offset = 1024 ──┘ ← 数据完全同步!

每当主库向从库传播N字节的写命令时:

  • 主库的master_repl_offset增加 N
  • 从库收到后,slave_repl_offset也增加 N

如果从库断线了,它的slave_repl_offset就会落后于master_repl_offset。差值就是"丢失的数据量"。

# 查看主库的复制偏移量127.0.0.1:6379>INFO replication# Replicationrole:master master_repl_offset:1024...# 查看从库的复制偏移量127.0.0.1:6380>INFO replication# Replicationrole:slave master_repl_offset:1024 slave_repl_offset:1024# 与主库一致,说明完全同步

踩坑提示:如果从库的slave_repl_offset长期追不上master_repl_offset,说明网络带宽不够或从库负载过高,需要排查原因。可以用redis-cli --latency检测网络延迟。

三、复制积压缓冲区:断线期间的命令暂存区

复制积压缓冲区(replication backlog)是主库维护的一个固定大小的FIFO环形缓冲区,默认1MB。主库传播给从库的每条写命令,都会同时存入这个缓冲区。

复制积压缓冲区(环形结构,默认1MB): ┌──────────────────────────────────┐ 写入 → │ cmd1 │ cmd2 │ cmd3 │ ... │ cmdN │ → 覆盖旧数据 └──────────────────────────────────┘ ▲ ▲ │ │ 偏移量: offset_start offset_end 当缓冲区满后,新数据从头部开始覆盖(环形)

当从库断线重连时,它告诉主库自己的slave_repl_offset,主库检查:

  • 如果这个 offset 对应的命令还在积压缓冲区里→ 部分重同步,把缓冲区里从 offset 到末尾的命令发给从库
  • 如果这个 offset 对应的命令已经被覆盖了→ 完整重同步,因为丢失的数据已经找不回来了
# 查看积压缓冲区大小127.0.0.1:6379>INFO replication repl_backlog_active:1 repl_backlog_size:1048576# 1MB = 1048576 bytesrepl_backlog_first_byte_offset:1 repl_backlog_histlen:1024

repl-backlog-size 调优建议

1MB 的默认值在大多数场景下偏小。如果网络偶尔闪断几秒,而主库写入量较大,1MB 可能不够暂存断线期间的命令。

推荐计算公式: repl-backlog-size ≈ 写入速率(bytes/s) × 预期最大断线时间(s) × 2 示例: 写入速率 = 5MB/s(通过 INFO stats 的 total_net_output_bytes 估算) 最大断线时间 = 60s 推荐大小 = 5 × 60 × 2 = 600MB → 建议设为 512MB 或 1GB
# 动态修改积压缓冲区大小(Redis 2.8+,不需要重启)CONFIG SET repl-backlog-size536870912# 512MB# 在 redis.conf 中永久配置repl-backlog-size 512mb

踩坑提示:修改repl-backlog-size会导致积压缓冲区重建,从库如果此时断线,必须全量重同步。建议在业务低峰期操作。另外,repl-backlog-size设为 0 表示禁用积压缓冲区,此时所有断线重连都会走全量同步。

四、服务器运行ID:我是谁,你是谁

每个Redis服务器启动时都会生成一个40位十六进制的随机字符串作为运行ID(runid)。

127.0.0.1:6379>INFO server# Serverrun_id:9b8a32b6882a94a0e6f6e9f7a3d2c1b0e5f4a7d8

runid 的作用是:从库断线重连时,确认自己连接的还是之前那个主库

为什么需要确认?想象一个场景:

┌──────────────────────────────────────────────┐ │ 场景:主库宕机后被人误重启 │ │ │ │ 1. 从库连接的是主库A (runid=aaa...) │ │ 2. 主库A宕机 │ │ 3. 主库A被重启 → runid 变为 bbb... │ │ 4. 从库重连,发现 runid 不匹配 │ │ 5. 结论:这不是之前的主库,必须全量同步! │ │ │ │ 因为新主库的数据集可能完全不同(比如恢复了不同的 │ │ RDB备份),积压缓冲区的内容也不再适用 │ └──────────────────────────────────────────────┘

如果从库发现 runid 和之前一致,说明主库没有被重启过,积压缓冲区是连续的,可以尝试部分重同步。

五、PSYNC判断逻辑:三步走决策

当从库断线重连主库时,PSYNC的判断逻辑如下:

从库发送: PSYNC <runid> <offset> │ │ │ └── 从库的复制偏移量 └── 之前主库的运行ID 主库收到后判断: ┌─────────────────────────────────┐ │ runid 是否匹配? │ └───────┬──────────────┬──────────┘ │ 否 │ 是 ▼ ▼ ┌──────────────┐ ┌─────────────────────┐ │ 完整重同步 │ │ offset对应的命令 │ │ (Full Resync)│ │ 是否在积压缓冲区中? │ └──────────────┘ └─────┬────────┬──────┘ │ 否 │ 是 ▼ ▼ ┌──────────┐ ┌──────────────┐ │ 完整重同步│ │ 部分重同步 │ │(Full) │ │ (Partial) │ └──────────┘ └──────────────┘

更完整的ASCII流程图:

从库 主库 │ │ │ PSYNC <old_runid> <slave_offset> │ │───────────────────────────────────────→│ │ │ │ ┌────────┴────────┐ │ │ runid == my_runid?│ │ └───┬─────────┬────┘ │ │No │Yes │ ┌────┘ └────┐ │ │ │ │ │ ┌────────┴──────────┐ │ │ │offset在backlog中? │ │ │ └───┬──────────┬────┘ │ │ │No │Yes │ ▼ ▼ ▼ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │+FULLRESYNC│ │+FULLRESYNC││+CONTINUE│ │ │ <runid> │ │ <runid> │ │ │ │ │ <offset> │ │ <offset> │ │ 发送缺失│ │ └────┬─────┘ └────┬─────┘ │ 的命令 │ │ │ │ └────┬────┘ │ ←─ RDB传输 + 命令传播 ────┘ │ │ │ │ │ │ │ ←─ RDB传输 + 命令传播 ──────────────────┘ │ │ ←─ 仅缺失命令传播 ─────────────────────────────────┘

从库首次复制时,因为没有 runid 和 offset,发送的是:PSYNC ? -1,主库收到后必然执行完整重同步。

六、PSYNC2:Redis 4.0的进化

PSYNC1有一个明显缺陷:当从库晋升为新主库后,其他从库必须和新主库做全量同步,因为新主库的 runid 和积压缓冲区都是从零开始的。

PSYNC2(Redis 4.0+)通过以下改进解决了这个问题:

特性PSYNC1PSYNC2
从库晋升后重同步必须全量可以部分重同步
积压缓冲区记录只记录当前主库的记录主库切换历史
runid仅当次运行ID引入replid(复制ID)
切换场景每次全量大部分可部分重同步

PSYNC2的核心改进:

  1. replid(复制ID):取代单一的 runid。从库晋升为主库后,会保留之前主库的 replid 作为replid2,并生成自己的新 replid。其他从库重连时,可以通过 replid2 匹配到旧主库的标识。

  2. 复制偏移量的持久化:PSYNC2会将复制偏移量保存到RDB文件中,重启后可以恢复之前的同步位置。

# PSYNC2相关的INFO字段127.0.0.1:6379>INFO replication master_replid:9b8a32b6882a94a0e6f6e9f7a3d2c1b0e5f4a7d8 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:1024 second_repl_offset:-1

踩坑提示:Redis 4.0之前版本的从库在故障转移后必须全量同步,这在大型集群中可能造成巨大的网络开销。如果你的集群数据量大,强烈建议升级到 Redis 4.0+。

七、总结与实践建议

PSYNC的出现让Redis复制的效率有了质的飞跃:

断线前 断线重连后 ┌──────────┐ ┌──────────────────┐ 旧版SYNC: │ 正常复制 │ ──────→│ 全量RDB传输(T_T) │ └──────────┘ └──────────────────┘ ┌──────────┐ ┌──────────────────┐ PSYNC部分重同步:│ 正常复制 │ ──────→│ 仅传缺失命令(^_^) │ └──────────┘ └──────────────────┘

实践建议

  1. 务必调大 repl-backlog-size:1MB 默认值在大多数生产环境远远不够
  2. 使用 Redis 4.0+:享受 PSYNC2 的红利,减少故障转移后的全量同步
  3. 监控复制偏移量差值master_repl_offset - slave_repl_offset持续增大说明从库跟不上
  4. 避免频繁断线:网络抖动会导致反复的全量/部分重同步,消耗资源
# 监控主从偏移量差异的脚本思路whiletrue;domaster_offset=$(redis-cli-hmaster INFO replication|grepmaster_repl_offset|head-1|cut-d:-f2)slave_offset=$(redis-cli-hslave INFO replication|grepslave_repl_offset|cut-d:-f2)echo"Lag:$((master_offset-slave_offset))bytes"sleep1done

PSYNC从机制上解决了断线重连的痛点,但复制远不止这些——下一篇我们将完整走一遍从slaveof到数据同步完成的全流程,看看心跳检测、命令传播这些环节是如何配合的。


上一篇【第40篇】旧版复制的硬伤——Redis 2.8之前为什么会反复全量同步
下一篇【第42篇】 复制全流程——从slaveof到数据同步完成


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

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

立即咨询