简单来说:
增量同步就是Master 只把比 Slave 新的数据发给 Slave,而不是发送全部数据。它像一个持续更新的直播流,或者我之前比喻的“每日更新期刊”。Slave 不用重新加载所有数据,只需要接收和应用这些新的更新。
这就像,你作家写的书,读者已经拿到了完整的最新版。你以后每天写点新东西,只需要把写的新章节发给他,他就能把新的章节加到书的后面,就不用我把整本书再重新打印一遍然后麻烦他再读一遍了。
为什么需要增量同步?
想象一下,你写一部百万字的小说,每天写几百字。如果每次粉丝(Slave)断线(比如手机信号不好,或者中途APP闪退了一下),然后他再连回来,你都得把这几百万字的完整小说重新给他背一遍,那得多麻烦?流量、时间和资源都会大量浪费。
增量同步就是为了解决这个问题:减少全量同步的次数,提高复制效率。
增量同步的核心部件:
在 Redis 中,实现增量同步有两个关键的协同工作部分:
-
Master 的
Replication Backlog
(复制积压缓冲区):- 这是一个特殊的“草稿箱”。你(作家 Master)每写好一句(执行一条写命令),除了发送给粉丝(如果有粉丝连接着),你还会把它暂时副本放在这个“草稿箱”里。
- 这个草稿箱是环形的,就像一个循环录像带。它有一个固定的大小(可以配置,默认是 1MB),当写的内容越来越多时,最老的内容就会被新内容覆盖掉。
- 这个草稿箱就是为了当你和粉丝(Master 和 Slave)的直播连接意外断开后,再重新连接时,可以方便地找到“从哪个位置(
offset
)开始发送新的更新”。
-
Replication Offset
(复制偏移量):- 这是一个“阅读进度条”。你把写好的内容一句一句地发出去了,你会记录你已经发了多少字(Master 的最新偏移量)。
- 你的每个粉丝(Slave)也会记录自己已经收到了多少字。
- 这个偏移量是一个单调递增的数字。它精确标识了主服务器数据流中某个点。Master 知道自己发到了哪里,Slave 知道自己收到了哪里。
-
Master Replication ID
(replid/runid):- 你的“作家笔名 ID”(或者你的身份证号)。在你没换笔名(Master 非重启,或者故障转移)的情况下,即使你暂停写作(数据有一段时间没更新),在“笔名”不变的前提下,你上次的写作“风格”(数据特征)也还在。
- 这是判断 Master 身份唯一性的关键。只有 Master ID 没变动,增量同步才有基础。
增量同步是如何进行的?
-
Slave 断线重连时(比如因网络波动):
- 粉丝(Slave)与你(Master)的电话断了。但是,他知道自己上次是“鬼才金庸”(Master
replid
)的读者,而且已经看完了第 130 章(offset
)。 - 当连接恢复后,粉丝立即打电话(发送
PSYNC <replid> <offset>
命令)给你,并说:“我是老粉丝,笔名是‘鬼才金庸’,我上次看到第 130 章了。”
- 粉丝(Slave)与你(Master)的电话断了。但是,他知道自己上次是“鬼才金庸”(Master
-
Master 的判断:
- 第一步:检查笔名 ID(replid)。你(Master)发现电话里说的笔名 ID 和你当前使用的笔名 ID 是一模一样的。
- “嗯,没换笔名,还是我的老读者!”
- 第二步:检查阅读进度 (offset) 是否在草稿箱 (Backlog) 里。 你(Master)马上去检查你的“草稿箱”(Replication Backlog)。
- 你发现你的草稿箱里保存了从第 120 章到当前你最新写的第 140 章的所有内容。
- 而粉丝小明说他看到第 130 章了。
- “太好了!你说的 130 章后面的那点更新(第 131 章到第 140 章我最新写的部分),都在我的草稿箱里呢,没被新的内容挤出去!”
- 第一步:检查笔名 ID(replid)。你(Master)发现电话里说的笔名 ID 和你当前使用的笔名 ID 是一模一样的。
-
增量发送:
- 你(Master)立刻告诉粉丝:“好啦,第 131 章到第 140 章的更新,我现在就报给你听!” (Master 回复
+CONTINUE
,然后直接从Replication Backlog
中提取offset
130之后的数据,以 AOF 命令流的形式发送给 Slave。) - 粉丝(Slave)收到后,将其追加到自己的小说集后面,并更新自己的阅读进度(
offset
)。
- 你(Master)立刻告诉粉丝:“好啦,第 131 章到第 140 章的更新,我现在就报给你听!” (Master 回复
动图模拟增量同步:
假设 offset
代表数据写入进程,Master 从 0 开始不断增加。
Replication Backlog 是一个固定大小的窗口,里面保存了最近的历史数据。
时间轴 -->Master 数据写入: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...]
Master 当前 Offset: 11Replication Backlog (假设大小为 5): [ 7, 8, 9, 10, 11 ] ^ 最老 ^ 最新Slave A (上次收到 9): 连接 - PSYNC <replid> 9Master 检查: replid 匹配,9 在 Backlog 内。✓Master 回复: +CONTINUEMaster 发送增量: [10, 11]
Slave A 更新到 11。成功增量同步!Slave B (上次收到 5):连接 - PSYNC <replid> 5Master 检查: replid 匹配,但 5 不在 Backlog 内 [7, 8, 9, 10, 11]。✗Master 回复: +FULLRESYNC ...Master 执行: 全量同步 (生成 RDB, 再发送从新 RDB 点开始的 AOF)
Slave B 更新到 11。被迫全量同步!
需要注意:
Replication Backlog
的大小配置很关键。 如果这个缓冲区太小,Slave 稍微掉队一点(断线时间稍长一点),其offset
就会超出缓冲区范围,导致增量同步失败,仍然退化为全量同步。所以需要根据网络情况和应用的写入 QPS(每秒查询率)来合理估算和设置这个积压缓冲区的大小。通常建议设置为 Slave 在断线重连前可能积累的最大写命令量。- 如果 Master 本身发生了重启(导致
runid
改变),或者发生了故障转移(Failover),新的 Master 会有新的runid
,即使它们的offset
看起来一致,Slave 也会被判断为需要进行全量同步。