Redis 主从复制深挖 · PSYNC、偏移量与复制积压缓冲

作者:杨帅      发布日期:2026.09.01

Redis · 主从复制

副本怎么追上主库

PSYNC、复制偏移量,与那个决定命运的环形缓冲

主库把每条写命令源源不断发给副本。可网络一抖、副本掉线又重连,它该从头全量拷一遍,还是只补上断线期间漏掉的那一小段?答案藏在一个叫 复制积压缓冲 的环形缓冲里。

▲ 重连时的抉择:偏移量还在窗口内 → 部分重同步;否则 → 全量PSYNC decision
副本 Replica 断线重连 主库 Master 检查积压窗口 PSYNC <replid> <offset> 复制流(按字节偏移量) 复制积压窗口 backlog(最近 N 字节) head replica offset ✓ 在窗口内 replica offset ✗ 已被覆盖 +CONTINUE · 部分重同步 只补发 [offset → head] 这一小段,不用 fork,极快 +FULLRESYNC · 全量重同步 fork 生成 RDB 全量快照 + 缓冲命令,代价高
窗口内:可部分重同步 已被覆盖:只能全量 head:主库当前偏移
01 · 复制怎么开始

先握手,再对齐

Redis 复制默认是异步的,副本默认只读。当你用 REPLICAOF <host> <port> 指定主库后,副本会发起一段握手,最后用 PSYNC 请求同步:

▲ 副本 → 主库 的握手序列handshake
PING主库在否 AUTH鉴权(如需) REPLCONF port告知端口 REPLCONF capa psync2声明能力 PSYNC请求同步

握手完成后,主库把每条写命令按 RESP 协议持续推流给副本,副本按序重放,两边数据保持一致。全新副本第一次连接会直接发 PSYNC ? -1——强制全量。

02 · 两把尺子

复制 ID 与偏移量

副本能不能"接着上次继续",全靠两个数字来判断:

  • 复制 ID(replid):一串伪随机字符串,标识一段数据血统。启动或"副本被提升为主库"时生成。
  • 复制偏移量(offset):复制流里的字节位置。主库有 master_repl_offset,副本记自己的;两者之差就是复制延迟

只要两个实例的 replid 相同、offset 也相同,它们的数据就完全一致。

i

PSYNC2(Redis 4.0)的巧思:当副本被提升为新主库,它会保留旧主库的 ID 到 master_replid2、再生成一个新 ID。这样老主库的其它副本,还能凭旧 ID 对新主库做部分重同步——故障切换后不必所有副本都全量重来。

03 · 环形缓冲

复制积压缓冲区

这是整个故事的主角。主库维护一个固定大小的环形缓冲(ring buffer),把最近发出的写命令都留一份在里面。副本掉线期间,主库照样把新命令写进这个缓冲。

副本重连时报出自己的 offset:如果它还落在缓冲窗口内,主库就把"缺的那一段"补发过去(部分重同步);如果它太旧、已被新命令覆盖,就只能全量重来。

# redis.conf repl-backlog-size 1mb # 环形缓冲大小,默认 1MB repl-backlog-ttl 3600 # 最后一个副本断开这么久后释放缓冲 # 该开多大?按 写入速率 × 想扛住的断线时长 估算: # 写 10 MB/s、想扛 30s 断线 → ~300 MB
打个比方

像广播电台的"最近 N 分钟回放缓存":你掉线几分钟回来,还能从回放里补上;掉线太久,回放早被新节目覆盖,只能从头重新收听整档节目。缓冲越大,能补的断线就越长。

04 · 两种重同步

部分 vs 全量

主库看完 PSYNC 请求,只会走两条路之一:

+CONTINUE · 部分重同步

只补缺口,最理想

  • offset 仍在积压窗口内、replid 匹配
  • 只发 [offset → head] 那一小段命令
  • 不用 fork、不传 RDB,极快,省带宽
+FULLRESYNC · 全量重同步

从头拷一遍,代价高

  • 首次连接,或 offset 已被覆盖 / replid 不符
  • 主库 fork → 生成 RDB → 发给副本加载
  • 其间新命令先缓冲,RDB 传完再补上
# 协议层就这么几句 Replica → Master: PSYNC <replid> <offset> Master → Replica: +CONTINUE <replid> # 部分:随后补发缺口命令 Master → Replica: +FULLRESYNC <replid> <offset> # 全量:随后发 RDB + 缓冲命令
05 · 全量的代价

为什么要尽量避免全量

全量重同步意味着一次 fork + RDB 生成 + 网络传输 + 副本加载——数据集越大越贵,还会给主库带来 fork 延迟。所以能部分就别全量。

当全量不可避免时,无盘复制(diskless)能省掉"写盘"这一步:主库把 RDB 直接从内存经网络流给副本,不落主库磁盘。现代版本默认开启:

repl-diskless-sync yes # 无盘复制:RDB 直接走网络 repl-diskless-sync-delay 5 # 等几秒,让多个副本共用同一份 RDB 流 repl-diskless-sync-max-replicas 0 # 并行服务的副本数上限(0=不限)
i

一个"省心"的小设计:副本掉线时会缓存住主库连接状态与自己的 offset。重连时先赌一把"积压缓冲还覆盖着我的 offset"——赌赢了就无缝续上、零 RDB;赌输了才丢弃缓存、走全量。

06 · 调优与监控

让复制少全量、少延迟

INFO replication 盯几个关键指标,按需调参:

指标 / 参数含义与动作
sync_full全量同步次数。频繁增长说明积压缓冲太小或断线太久。
sync_partial_ok / _err_err 持续上涨 → 部分重同步失败、退化成全量 → 调大 repl-backlog-size
master_repl_offset / slave offset两者之差 = 复制延迟(lag),越小越好。
master_link_status副本视角的主从链路状态(up/down)。
min-replicas-to-write
min-replicas-max-lag
健康副本不足或延迟过大时,让主库拒绝写入,防"写了没人接"。
WAIT numreplicas timeout阻塞到 N 个副本确认了当前 offset,换更强的持久性保证(仍非强一致)。
按写入速率定缓冲repl-backlog-size ≈ 写入速率 × 想扛住的断线时长,别用默认 1MB 硬扛。
盯 sync_partial_err它涨就是"缓冲不够"的信号,比事后排查全量风暴更早。
异步复制 ≠ 零丢失主库确认写入后可能还没到副本;要更强保证用 WAIT 或 min-replicas-*。
大实例警惕全量风暴网络抖动导致多副本同时全量,会给主库叠加 fork 与带宽压力。
收尾 · 一句话

断了几秒,别从头再来

主从复制靠 复制 ID + 偏移量 记住"同步到哪了",靠 复制积压缓冲 这个环形回放,让短暂断线只需部分重同步补个缺口,而非昂贵的全量。把缓冲按写入速率调够、盯住 sync_partial_err,就能让副本"断了也能接着追"。

PSYNC replid+offset · 在积压窗口内 → +CONTINUE 部分重同步(不 fork)· 否则 → +FULLRESYNC 全量(fork+RDB,可无盘)· PSYNC2 让故障切换后仍能部分同步

内容依据 Redis 官方文档整理 · redis.io/docs · Replication

配置项默认值以对应 Redis 版本为准;示例数值为说明性示意。