🎯 一、为什么需要复制
高可用
一台挂了,副本顶上,系统不中断。
读扩展
多个副本分担读流量(一写多读)。
就近低延迟
副本放在离用户近的机房/地域。
备份容灾
天然多份拷贝,抗单点数据丢失。
🕸️ 二、三种复制拓扑
| 拓扑 | 写入 | 一致性 | 优点 | 缺点 | 代表 |
|---|---|---|---|---|---|
| 单主(主从) | 仅主可写 | 较强 | 简单、无写冲突 | 主是瓶颈/单点 | MySQL 主从、Redis |
| 多主 | 多节点可写 | 需解决冲突 | 就近写、容错好 | 写冲突/脑裂 | MySQL MGR、Cassandra |
| 无主 | 任意节点可写 | 最终一致 | 极高可用、易扩展 | 读需协调/可能脏读 | DynamoDB、Cassandra |
🔁 三、同步 vs 异步复制
同步复制(Synchronous)
主库等所有副本都确认写入才返回成功。优点:不丢数据、强一致;缺点:任一副本慢/挂都阻塞写入,可用性差。常用于金融核心(至少 1 个同步副本)。
异步复制(Asynchronous)
主库写入本地即返回,副本"稍后"追。优点:写入快、可用高;缺点:主挂可能丢未同步数据,副本读到旧值。绝大多数互联网系统默认选它。
半同步(折中)
主库等"至少 1 个/部分"副本确认即可,兼顾性能与安全性(MySQL 半同步、Raft 的多数派)。
⏳ 四、异步复制的"延迟"陷阱
异步复制下,副本落后于主库是常态。这会引出三个经典一致性异常,面试必考。
① 读己之写(Read-Your-Writes)
用户刚改完自己的头像,刷新却看到旧的——因为读到了还没追上来的副本。解法:写后一段时间/关键读走主库,或用"sticky session"把同一用户路由到含最新数据的副本。
② 单调读(Monotonic Reads)
用户连续刷新,数据却"时光倒流"(先看到新值再看到旧值)——因为两次读打到了不同进度的副本。解法:按用户固定路由到同一副本。
③ 一致前缀读(Consistent Prefix)
先发生的事件比后发生的事件更晚被看到(如问答中先看到"回答"才看到"提问")。解法:按因果关系(因果序列)路由相关数据的读。
这三类问题都属于"最终一致性"下的用户体验问题,靠"会话/因果/用户级路由"缓解,而非强一致。
🧭 五、怎么选型
经验法则:① 写少读多、要简单 → 单主;② 多地域写、容忍冲突 → 多主/无主;③ 要强一致且高可用 → 用 Raft/Paxos 类共识(见共识目录)做复制;④ 极致可用、能接受最终一致 → 无主。
🎯 六、面试高频追问
主从复制下主库挂了怎么办?
触发故障转移(failover):选一个从库提升为新主(需先追平数据),更新路由。难点:脑裂(旧主复活)、数据丢失(异步未同步部分)。生产靠 Orchestrator/MHA/共识选主。
主从延迟怎么监控和治理?
监控
Seconds_Behind_Master / 复制位点差;治理:并行复制、降低大事务、读分流策略、业务上接受最终一致。复制和分片是什么关系?
正交但可以叠加:分片解决"数据量/算力"(水平切),复制解决"高可用/读扩展"(多副本)。一个分片内部通常又是一主多从。