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