bigywater.tech
Redis · 55

部分重同步

部分重同步是复制里最值钱的优化:从库断线重连后,只补它缺的那一段命令。这篇不看 INFO 里的计数,而是自己写一个假从库,直接用 TCP 和主库说 PSYNC,把主库回的每一段字节原样打印出来,看部分重同步到底传了什么。

83 字节
部分重同步补发的全部内容:断线期间的 3 条写命令(RESP 原文)
+CONTINUE
主库同意部分重同步的回复;+FULLRESYNC 是退回全量
offset + 1
PSYNC 第二个参数:从库「下一个想要的」字节位置

第一次:PSYNC ? -1

实测 · 本机执行输出
主库回复: +FULLRESYNC 510b3748d1c60961e238faf0036dca6a7cd42881 0
随后是 RDB 负载:长度行 $207,前 9 字节 = b'REDIS0015'(RDB 魔数 + 版本)

假从库先发 PING、REPLCONF listening-port、REPLCONF capa eof capa psync2 完成握手,再发 PSYNC ? -1。主库回 +FULLRESYNC <replid> 0,随后是 $207 字节的 RDB 数据(这里为了方便解析,主库设置 repl-diskless-sync no,走磁盘文件方式;无盘方式的格式不同,见第 58 篇)。开头 9 字节是 REDIS0015,即 RDB 魔数加版本。

之后:命令流

实测 · 本机执行输出
*2 ⏎ $6 ⏎ SELECT ⏎ $1 ⏎ 0 ⏎ *3 ⏎ $3 ⏎ SET ⏎ $1 ⏎ a ⏎ $1 ⏎ 1 ⏎ *2 ⏎ $4 ⏎ INCR ⏎ $1 ⏎ n ⏎ *4 ⏎ $5 ⏎ LPUSH ⏎ $1 ⏎ l ⏎ $1 ⏎ x ⏎ $1 ⏎ y ⏎ 
流的总长度 107 字节;从库当前 offset = 0 + 107 = 107
主库 master_repl_offset = 107

全量同步完成之后,主库把每条写命令按原样、用 RESP 编码发过来。第一条是 SELECT 0(先指明数据库),然后是 SET、INCR、LPUSH,合计 107 字节,⏎ 表示 \r\n。从库的 offset 就是这些字节数的累计,与主库的 master_repl_offset 对上。

断线之后:PSYNC replid offset+1

实测 · 本机执行输出
主库 master_repl_offset = 190
实测 · 本机执行输出
主库回复: +CONTINUE 510b3748d1c60961e238faf0036dca6a7cd42881
补发的内容: *3 ⏎ $3 ⏎ SET ⏎ $1 ⏎ x ⏎ $1 ⏎ 1 ⏎ *4 ⏎ $5 ⏎ LPUSH ⏎ $1 ⏎ l ⏎ $1 ⏎ a ⏎ $1 ⏎ b ⏎ *2 ⏎ $3 ⏎ DEL ⏎ $1 ⏎ a ⏎ 
补发了 83 字节(= 断开期间写入的字节数);从库 offset → 190

主库回 +CONTINUE <replid>,紧跟着的就是断线期间的 3 条命令原文,83 字节。没有 RDB,没有多余的东西。请求里的 offset 是「已收到的字节数 + 1」,即下一个想要的字节位置。

什么时候只能全量

实测 · 本机执行输出
(先在主库写入约 100 KB:master_repl_offset = 130190,backlog 起点 first_byte_offset = 102201)
replid 不认识                   PSYNC deadbeef… 191 → +FULLRESYNC 510b3748d1c60961e238faf0036dca6a7cd42881 130190
offset 比主库还大                 PSYNC 510b3748… 100000190 → +FULLRESYNC 510b3748d1c60961e238faf0036dca6a7cd42881 130190
offset 太旧(早于 backlog 起点)     PSYNC 510b3748… 191 → +FULLRESYNC 510b3748d1c60961e238faf0036dca6a7cd42881 130190
主库 stats: {'sync_full': 4, 'sync_partial_ok': 1, 'sync_partial_err': 3}

三种情况主库都回 +FULLRESYNC:replid 不认识;offset 比主库当前的还大;offset 早于 backlog 的起点(这里把 backlog 设成 16 KB、先写入约 100 KB 之后,请求一个很早的 offset)。主库 stats 里 sync_partial_err 累计到 3,sync_partial_ok 1。

分步演示:一次 PSYNC 的字节

部分重同步
本步的报文

    本文用到的工具与边界

    • 本机 macOS 27 / Redis 8.10.2 独立实例(本机回环端口,save 关闭),redis-cli 风格输出由自写的 RESP 客户端打印;计时用 time.perf_counter,数字每次运行会有小幅抖动。假从库是我手写的 Python 套接字程序,只做握手与 PSYNC,不解析 RDB、不回复 REPLCONF ACK
    • 没有验证:假从库长时间不发 REPLCONF ACK 时主库的行为(超时断开等)、diskless 传输格式(第 58 篇)、replid2 与级联复制下的 PSYNC(第 52 篇部分覆盖)
    京ICP备2026067502号-1京公网安备11010502063487号