副本怎么追上主库
主库把每条写命令源源不断发给副本。可网络一抖、副本掉线又重连,它该从头全量拷一遍,还是只补上断线期间漏掉的那一小段?答案藏在一个叫 复制积压缓冲 的环形缓冲里。
先握手,再对齐
Redis 复制默认是异步的,副本默认只读。当你用 REPLICAOF <host> <port> 指定主库后,副本会发起一段握手,最后用 PSYNC 请求同步:
握手完成后,主库把每条写命令按 RESP 协议持续推流给副本,副本按序重放,两边数据保持一致。全新副本第一次连接会直接发 PSYNC ? -1——强制全量。
复制 ID 与偏移量
副本能不能"接着上次继续",全靠两个数字来判断:
- 复制 ID(replid):一串伪随机字符串,标识一段数据血统。启动或"副本被提升为主库"时生成。
- 复制偏移量(offset):复制流里的字节位置。主库有
master_repl_offset,副本记自己的;两者之差就是复制延迟。
只要两个实例的 replid 相同、offset 也相同,它们的数据就完全一致。
PSYNC2(Redis 4.0)的巧思:当副本被提升为新主库,它会保留旧主库的 ID 到 master_replid2、再生成一个新 ID。这样老主库的其它副本,还能凭旧 ID 对新主库做部分重同步——故障切换后不必所有副本都全量重来。
复制积压缓冲区
这是整个故事的主角。主库维护一个固定大小的环形缓冲(ring buffer),把最近发出的写命令都留一份在里面。副本掉线期间,主库照样把新命令写进这个缓冲。
副本重连时报出自己的 offset:如果它还落在缓冲窗口内,主库就把"缺的那一段"补发过去(部分重同步);如果它太旧、已被新命令覆盖,就只能全量重来。
像广播电台的"最近 N 分钟回放缓存":你掉线几分钟回来,还能从回放里补上;掉线太久,回放早被新节目覆盖,只能从头重新收听整档节目。缓冲越大,能补的断线就越长。
部分 vs 全量
主库看完 PSYNC 请求,只会走两条路之一:
只补缺口,最理想
- offset 仍在积压窗口内、replid 匹配
- 只发 [offset → head] 那一小段命令
- 不用 fork、不传 RDB,极快,省带宽
从头拷一遍,代价高
- 首次连接,或 offset 已被覆盖 / replid 不符
- 主库 fork → 生成 RDB → 发给副本加载
- 其间新命令先缓冲,RDB 传完再补上
为什么要尽量避免全量
全量重同步意味着一次 fork + RDB 生成 + 网络传输 + 副本加载——数据集越大越贵,还会给主库带来 fork 延迟。所以能部分就别全量。
当全量不可避免时,无盘复制(diskless)能省掉"写盘"这一步:主库把 RDB 直接从内存经网络流给副本,不落主库磁盘。现代版本默认开启:
一个"省心"的小设计:副本掉线时会缓存住主库连接状态与自己的 offset。重连时先赌一把"积压缓冲还覆盖着我的 offset"——赌赢了就无缝续上、零 RDB;赌输了才丢弃缓存、走全量。
让复制少全量、少延迟
用 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,换更强的持久性保证(仍非强一致)。 |
断了几秒,别从头再来
主从复制靠 复制 ID + 偏移量 记住"同步到哪了",靠 复制积压缓冲 这个环形回放,让短暂断线只需部分重同步补个缺口,而非昂贵的全量。把缓冲按写入速率调够、盯住 sync_partial_err,就能让副本"断了也能接着追"。


