bigywater.tech
Redis · 52

PSYNC2 复制协议

早期的 Redis 复制里,只要主从关系一变——比如故障转移——所有从库都要做一次全量同步。PSYNC2 让节点记住「我上一任主库的复制 ID 和 offset」,换主之后可以接着同步。这篇用三个真实节点、一次真实的提升,看这些 ID 和 offset 怎么变,并对照哪些场景仍然要全量同步。

2 个 ID
被提升的从库同时持有 master_replid(新)与 master_replid2(旧)
+1
second_repl_offset 比提升时的 offset 大 1(31803 → 31804)
full +0
C 改跟随新主 B 时,B 的 sync_full 增量(partial_ok +1)

同一段复制历史:replid 与 offset

实测 · 本机执行输出
A: role=master replid=6a40066f… replid2=0… offset= 31803 second_offset=-1
B: role=slave  replid=6a40066f… replid2=0… offset= 31803 second_offset=-1
C: role=slave  replid=6a40066f… replid2=0… offset= 31803 second_offset=-1
A(主): sync_full=2 sync_partial_ok=0

master_replid 是一段复制历史的标识,master_repl_offset 是这段历史里已复制的字节位置。主库和它的从库共享同一个 replid,offset 也一致;A 上的 sync_full=2 是 B、C 最初各做了一次全量同步。

提升 B:新 ID + 保留旧 ID

实测 · 本机执行输出
A: role=master replid=6a40066f… replid2=0… offset= 31803 second_offset=-1
B: role=master replid=551b2c4d… replid2=6a40066f offset= 31803 second_offset=31804
B 的 replid2 == A 的旧 replid:True;second_repl_offset = 31804(提升时 offset 31803 + 1)

B 执行 REPLICAOF NO ONE 之后:master_replid 换成新的(551b2c4d…),旧的存入 master_replid2,second_repl_offset = 31804,正好是提升时 offset(31803)加 1。两个字段合起来的含义是:用旧 replid 来请求、并且请求的起始位置不超过 second_repl_offset 的从库,可以接受(下一步日志里 C 请求的起始位置就是 31804,正好等于 second_repl_offset)。

C 改跟随 B:部分重同步

实测 · 本机执行输出
B(新主): sync_full=0 sync_partial_ok=1  (变化: full +0, partial_ok +1)
B 的日志:
   Partial resynchronization not possible (no cached master)
   Partial resynchronization request from 127.0.0.1:7154 accepted. Sending 0 bytes of backlog starting from offset 31804.
C: role=slave  replid=551b2c4d… replid2=6a40066f offset= 31803 second_offset=31804

C 带着旧 replid 和自己的 offset 向 B 发 PSYNC,B 的日志是 Partial resynchronization request from 127.0.0.1:7154 accepted. Sending 0 bytes of backlog starting from offset 31804:接受,需要补的数据是 0 字节。B 的 sync_full 增量 0、sync_partial_ok 增量 1。日志里还有一行 Partial resynchronization not possible (no cached master),那是 B 自己(刚被提升、没有缓存的主库)的一条记录,不是对 C 的拒绝。

旧主降级为从库重新加入

实测 · 本机执行输出
A 重新加入之后 B: full +0, partial_ok +1
dbsize A/B/C: 1500 1500 1500
A: role=slave  replid=551b2c4d… replid2=6a40066f offset= 47606 second_offset=31804
B: role=master replid=551b2c4d… replid2=6a40066f offset= 47606 second_offset=31804
C: role=slave  replid=551b2c4d… replid2=6a40066f offset= 47606 second_offset=31804

B 上又写了 500 个 key;旧主 A 用 REPLICAOF 挂到 B:仍然是部分重同步(full +0、partial_ok +1),三个节点 dbsize 都是 1500,replid 全部收敛为新主的 551b2c4d…。

对照:毫无关系的节点只能全量

实测 · 本机执行输出
B: full +1, partial_ok +0

新节点 D 有自己的复制历史,replid 与 B 的 replid、replid2 都对不上,B 只能全量同步(sync_full +1)。所以「换主可以部分重同步」有前提:两边的历史在某个 offset 之前是同一条。

分步演示:replid 与 replid2 的变化

PSYNC2
本步变化的节点

    本文用到的工具与边界

    • 本机 macOS 27 / Redis 8.10.2 独立实例(本机回环端口,save 关闭),redis-cli 风格输出由自写的 RESP 客户端打印;计时用 time.perf_counter,数字每次运行会有小幅抖动。A、B、C、D 四个独立实例;「提升」用 REPLICAOF NO ONE 手动完成,没有经过 Sentinel 或 Cluster
    • 没有验证:backlog 不够大时同样的场景是否退回全量(见第 53 篇的 backlog 实验)、级联复制(从库的从库)下的 replid 传播、Redis 早于 4.0 的行为
    京ICP备2026067502号-1京公网安备11010502063487号