主库写完一条 SET
后立刻把连接切到从库读,偶尔能读到旧值——这不是客户端
bug,而是 Redis 复制默认异步 的必然结果。混合持久化
钉住了单机崩溃窗口;复制回答的是 多副本之间
数据何时对齐。运维里「从库分担读流量」与开发里「写后立刻读」若不在同一语义层讨论,故障复盘往往变成互相甩锅。
本文是「Redis / 缓存内核」系列第 12 篇(共 16 篇)。→ 系列目录
版本锚定:Redis OSS 7.4 / 8.x;源码路径以
src/replication.c为准;配置见redis.conf的REPLICATION段。
一、问题从哪来:异步复制不是「慢一点的同步」
经典主从复制模型里,主节点先本地提交写,再把变更流推给副本;副本应用流的速度可以落后主节点任意时长(网络抖动、副本 CPU、大 key 回放)。这与 Gray 在 Notes on Database Operating Systems(1978)里归纳的 primary-backup 异步复制 一脉相承:主库不等待副本确认即向客户端返回成功——复制延迟是设计特性,不是实现瑕疵。
Redis 官方 Replication 文档写明:副本通过
PSYNC 携带旧 replication ID
与已处理 offset,主库尽量只发增量;若 backlog 不足或 ID
未知,则
全量重同步(FULLRESYNC,通常走
RDB 快照流)。SYNC
为旧协议,不支持部分重同步,新实例以 PSYNC
为主。
与 缓存架构 的 Cache-Aside 对照:应用「写 DB + 删缓存」假设缓存 miss 后读到的是 DB 真值;若读路径改走 滞后副本,等价于在缓存与 DB 之间又插了一层 可能陈旧的只读视图,击穿/雪崩之外又多了一维 复制滞后。
二、复制数据流与 replication.c 锚点
主库每执行一条会改变数据集的命令,经
replicationFeedSlaves() 把 RESP 命令流 fan-out
到所有已连接副本,并同时写入主库的 replication
backlog(环形缓冲)。副本侧
syncWithMaster() 驱动握手与流式消费。
sequenceDiagram
participant Client
participant Master
participant Backlog
participant Replica
Client->>Master: SET k v
Master->>Master: apply command
Master->>Backlog: append repl stream
Master->>Replica: propagate command
Master-->>Client: OK
Note over Replica: apply may lag behind OK
Replica-->>Client: GET k (optional read path)
| 函数 / 命令 | 文件 | 作用 |
|---|---|---|
replicationFeedSlaves() |
replication.c |
主库向副本列表广播命令流并写 backlog |
addReplyReplicationBacklog() |
replication.c |
部分重同步时从 backlog 按 offset 回放 |
syncCommand() |
replication.c |
处理 SYNC / PSYNC 握手 |
syncWithMaster() |
replication.c |
副本侧连接主库、解析 +CONTINUE /
+FULLRESYNC |
readSyncBulkPayload() |
replication.c |
全量同步时接收 RDB 或 diskless 流 |
PSYNC 握手结果在源码中分为
PSYNC_CONTINUE(增量继续)、PSYNC_FULLRESYNC(全量)、PSYNC_NOT_SUPPORTED(回退
SYNC)。主库回复 +CONTINUE
时副本从已知 offset
追赶;+FULLRESYNC <replid> <offset>
则触发新一轮快照传输——对大实例,全量同步的 fork +
网络 + 加载 成本远高于追 backlog。
三、Replication backlog:部分重同步的内存账
redis.conf 默认
repl-backlog-size 1mb:主库维护固定大小环形缓冲,记录最近
replication 字节流。副本断连重连后若请求的 offset
仍在环内,主库只补发缺口;若已被覆盖或
replid 不匹配(主库重启),则
FULLRESYNC。
工程上 backlog 大小的粗算(官方注释与运维文档共识,非性能 benchmark):
\[ \text{backlog\_size} \gtrsim \text{峰值写带宽(字节/秒)} \times \text{可容忍断连时长(秒)} \]
| 配置项 | 默认(7.4 redis.conf) |
含义 |
|---|---|---|
repl-backlog-size |
1mb |
环形缓冲容量;有至少一个副本连接时才分配 |
repl-backlog-ttl |
3600 |
最后一个副本断开后,经过该秒数释放
backlog;0 表示永不释放 |
repl-diskless-sync |
依版本与构建 | 全量同步是否经 socket 直传 RDB,减轻磁盘尖刺 |
INFO replication 中的
sync_partial_ok / sync_partial_err
可观察部分重同步成败频率——sync_partial_err
持续升高通常意味着 backlog
相对写速率过小,而非网络「偶发」可忽略。
Redis 4.0+ 还支持 failover 后新主用 旧主的 replid 片段 服务仍记得旧 ID 的副本(官方 Partial sync after restarts and failovers),降低 Sentinel/手动故障转移后的全量同步风暴。这与「主库重启必全量」的早期印象不同,但 不能 推导出「任意拓扑下都不会 FULLRESYNC」。
四、副本只读与读路径语义
replica-read-only yes(默认)时,副本拒绝非主从复制来源的写命令(READONLY
错误)。副本仍可执行部分管理类命令,但
业务写必须走主库。
读副本时的关键配置与行为:
| 机制 | 说明 |
|---|---|
replica-serve-stale-data yes(默认) |
主从断连期间副本仍可服务读,数据可能陈旧 |
replica-read-only |
阻止客户端误写副本 |
WAIT |
客户端可阻塞等待至少 N 个副本确认 offset(非默认路径,有延迟代价) |
min-replicas-to-write /
min-replicas-max-lag |
主库侧写入门禁:副本不足或滞后过大时拒绝写 |
读己之写(read-your-writes):同一会话写主库后读副本,Redis 默认不保证。Terry et al. Session Guarantees for Weakly Consistent Replicated Data(PDIS 1994)把 session 级单调读/写己之写等形式化;Redis 复制协议 未 实现 session stickiness 或因果元数据,应用若需要 RYW,必须:写后读主、同连接读主、客户端跟踪 repl offset 并等待副本追上(复杂且少见),或接受陈旧读。
这与 PACELC 框架(Abadi, IEEE Computer 2012)在「分区不发生」时的 延迟—一致性 权衡一致:把读流量挪到副本是在用 staleness 换 主库 CPU/连接,不是免费午餐。
五、全量同步、diskless 与持久化交叉
全量重同步路径与 RDB
共享 fork() 子进程生成快照语义:大
used_memory 时 COW 压力与 fork
延迟 会同时打击主库(见第 15
篇)。repl-diskless-sync 与
repl-diskless-load(版本相关)改变 RDB
是经磁盘还是 socket
管道,不改变「全量比增量昂贵」的数量级判断。
副本加载 RDB 期间同样阻塞对外服务(除非配置允许
stale)。运维上应把 避免 FULLRESYNC
当作容量规划项:稳定网络、足够
backlog、控制主库频繁重启、Sentinel 故障转移后观察
master_link_status。
六、常见误解
| 误解 | 事实 |
|---|---|
| 「有从库就不会丢写」 | 异步复制下主库 OK
只表示主库已应用;主库宕机且未同步到副本的写会丢,除非用
WAIT 或同步复制产品特性 |
| 「PSYNC 总能增量」 | backlog 环覆盖或 replid 变化即
FULLRESYNC;写带宽高而
repl-backlog-size 仍为 1mb 时极易失败 |
| 「从库只读就不会不一致」 | 只读指不能写;读到的仍是滞后视图,与主库之间无线性一致性 |
| 「复制等于持久化」 | 复制解决的是 多副本可用性;副本内存里的数据同样会在全集群故障时丢失,除非另有 RDB/AOF(第 9–11 篇) |
| 「读写分离不用改代码」 | 写后读若打到副本,需应用层路由或接受陈旧;Cache-Aside 删缓存也不能修复副本滞后 |
七、学术谱系、争论与开放问题
谱系:Gray 主备异步复制 → Redis
PSYNC(Salvatore Sanfilippo 设计,官方文档与
replication.c)→ 4.0+ failover 部分同步 →
7.x/8.x diskless 与 IO 线程复制侧优化(细节见 release
notes,本篇不逐 commit 展开)。
争论:
- 异步复制 vs 同步确认:
WAIT把复制推进客户端延迟路径,提升「已复制」语义但降低可用性与尾延迟;与 Dynamo 系「客户端可调 QUORUM」类似,Redis 选择默认异步以保主库低延迟。 - 读副本是否值得:副本滞后在写多读少、强 RYW 场景下可能不如直接扩主库或加本地缓存;读副本适合 可容忍陈旧 的只读聚合(计数、画像),不适合 写后立刻读 的会话状态。
开放问题:
- 应用层如何以低成本实现「副本 offset ≥ 客户端已知写入 offset」的栅栏,而不过度阻塞(无统一 Redis 内建 session API)。
- 多副本链式复制(副本的副本)放大滞后;深度链是否仍应用读分担,缺乏与业务 SLO 绑定的公开基准(本篇不给出伪造 QPS 数字)。
八、运维检查清单(无伪造输出)
在已有 redis-server
的环境按序执行;数值以你机器为准:
# 复制拓扑与滞后
redis-cli INFO replication
redis-cli ROLE
# 部分重同步成败(sync_partial_err 持续涨 → 加大 backlog)
redis-cli INFO stats | egrep 'sync_partial'
# 运行时调 backlog(需评估内存)
redis-cli CONFIG GET repl-backlog-size
redis-cli CONFIG GET repl-backlog-ttl
# 副本只读与陈旧读
redis-cli CONFIG GET replica-read-only
redis-cli CONFIG GET replica-serve-stale-data判读要点:master_repl_offset 与
slave_repl_offset
差值即滞后字节量;master_link_status:down
时若仍
replica-serve-stale-data yes,读路径可能返回
断连前 的视图。写后读会话应
显式路由到主库,不要假设负载均衡「轮询即可」。
九、与 Cluster 的衔接
单主复制解决 读扩展与故障转移(配合 Sentinel,第 16 篇),不解决 写扩展。数据量或写 QPS 超过单机时进入 Cluster 分片:复制在 每个 hash slot 的主从对 上仍成立,但多 key 事务与读路由规则被 slot 约束改写。
十、小结
三句话小结:
- Redis 复制默认 异步:主库
OK不蕴含副本已应用,读副本可能陈旧,与 Cache-Aside 的「删缓存后读」不是同一层保证。 PSYNC+ replication backlog 把短暂断连从「全量 RDB」降为「追增量」,backlog 需按写带宽×断连时长规划,sync_partial_err是过小信号。replica-read-only只防误写,不提供读己之写;要强会话语义应写后读主,或显式WAIT并承担延迟。
下一篇进入分片:Cluster:16384 slot · MOVED/ASK。
参考资料
规范与源码(A 级)
- Redis Documentation,
Replication(
PSYNC、FULLRESYNC、部分同步与 failover)。 - Redis OSS 7.4
redis.conf,REPLICATION段(repl-backlog-size、repl-backlog-ttl、replica-read-only)。 - Redis OSS 7.4
src/replication.c:replicationFeedSlaves、addReplyReplicationBacklog、syncCommand、syncWithMaster。
论文与经典文献(A 级)
- Gray, J., Notes on Database Operating Systems,1978(主备复制与异步提交模型)。
- Terry, D. B. et al., Session Guarantees for Weakly Consistent Replicated Data, PDIS 1994(session / read-your-writes 形式化)。
辅助(B 级)
- Abadi, D. J., Consistency Tradeoffs in Modern Distributed Database System Design, IEEE Computer 2012(PACELC;对照延迟—一致性直觉)。
- 混合持久化、缓存架构。
返回 系列目录 | 上一篇:混合持久化 | 下一篇:Cluster
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【Redis / 缓存内核】TTL 与过期:expires dict · 惰性/主动删除 · lazyfree
拆解 Redis 7.4 的 TTL 机制:expires 辅助表、lookup 路径惰性删除、activeExpireCycle 自适应采样,以及 lazyfree 异步释放大对象对延迟的影响。
【Redis / 缓存内核】maxmemory 策略:近似 LRU/LFU 与 MEMORY DOCTOR
从 evict.c 拆解 volatile/allkeys 与 LRU/LFU/random/ttl 策略矩阵、24 位时钟近似 LRU、淘汰池采样,并对照算法篇真 LRU 与 MEMORY DOCTOR 信号。
【Redis / 缓存内核】RDB:fork COW · BGSAVE · 快照格式
从 rdb.c 拆解 SAVE/BGSAVE 的 fork 写时复制语义、子进程快照路径,以及 REDIS 魔数与 opcode 锚定的 RDB 格式边界。
【Redis / 缓存内核】AOF:命令日志 · rewrite · appendfsync
从 aof.c 拆解 AOF 多段清单、feedAppendOnlyFile 写路径、后台 rewrite 与 appendfsync always/everysec/no 各自的丢失窗口及 fsync 失败语义。