复制基础:主从、多主与无主

03-一致性与复制 核心 约 20 分钟 #复制#主从#多主#无主 更新 2026-10-02
当前状态:未学
本文基于模型知识整理(生成时未联网核对),关键结论建议对照经典文献复核。

一句话定义

复制是在多个节点上保留同一数据的冗余副本以提升可用性与读性能,拓扑上分为主从(唯一写入点)、多主(多写入点)与无主(客户端写多数派)三种范式,各自在一致性与可用性上做出不同取舍。

为什么重要

复制是一切分布式存储的第一性机制:不复制则单点必死,复制则立刻引入"副本间不一致"问题。后续的一致性模型(kp-013)、quorum(kp-014)、共识(kp-017)都是在回答"复制带来的不一致怎么管"。

前置知识

kp-006(不可靠网络与复制滞后)。

核心概念

  • 主从复制(leader-based):写都走 leader,leader 把变更日志同步给 follower;最常见(MySQL 主从、Redis 主从、Kafka 分区副本)。

- 同步复制:leader 等 follower 确认,一致性最强,可用性最差(follower 挂则写阻塞)。 - 异步复制:leader 写完即返回,failover 时可能丢最近写入(复制滞后窗口内)。 - 半同步:至少一个 follower 确认才返回,折衷。

  • 多主复制(multi-leader):多个中心各接写,之间互相同步;适合跨机房/离线场景(协作编辑、手机本地库),代价是写冲突常态化。
  • 无主复制(leaderless):客户端并行写 n 个副本中的 w 个、读 r 个(Dynamo 风格),没有唯一写点;配合 sloppy quorum 与 hinted handoff 提高写入可用性。

原理与机制

复制滞后的三类客户端异常(主从异步复制的必然结果):

  1. 读己之写(read-your-writes)失效:写主后立刻读从,读到旧值。
  2. 单调读(monotonic reads)失效:两次读落到不同从库,第二次反而更旧,"时光倒流"。
  3. 一致前缀读(consistent prefix read)失效:因分区延迟不同,后写的答案先于问题被读到。

对应解法:会话粘滞路由(同一用户固定读其写入的主/已同步从)、读从库前检查复制位点、分区元数据排序(详见 kp-016)。

failover 的危险窗口:主挂 → 选新主(kp-009)→ 旧主可能复活(认为自己是主)。异步复制下新主缺最后一段日志,旧主复活会带着"更多"数据覆盖——即丢失已确认写入。成熟系统用 fencing token + 旧主看到新 term 自降级(kp-021)来收敛。

图示

主从:  Client → L ──log──► F1, F2        (单写点,读可分散)
多主:  DC1: L1 ⇄ L2: DC1'                (多写点,需冲突解决 kp-015)
无主:  Client ──w──► {n1,n2,n3}  写3副本中2个
       Client ──r──► 读3副本中2个,取最新 (kp-014 quorum)

直观类比

主从像总店统一配方、分店照抄菜单(抄写需要时间,分店菜单可能旧);多主像各城市分公司各自改产品手册再互相同步(改到同一页就打架);无主像把重要文件同时交给三个保管人中的任意两个,取的时候问三个人按最新版本取。

实例或案例

  • MySQL/PostgreSQL 流复制 + Redis 主从:主从范式的代表,读扩展为主。
  • CouchDB/Cassandra(Dynamo 血统):无主 + quorum + 冲突容忍。
  • 跨数据中心:支付宝异地多活采用单元化多主思路,冲突靠业务规则收敛。

常见误区

  • 误区一:"主从异步复制没风险"。failover 丢窗口期写入是结构性风险,金融场景必须半同步或共识复制。
  • 误区二:"读从库不影响正确性"。存在滞后异常(见机制节),读敏感场景必须声明会话保证。
  • 误区三:"无主系统没有 leader 所以没有单点问题"。协调复杂度被转移给客户端/协调层,冲突处理与修复的成本并没有消失。

与其他知识点的关系

  • kp-009:failover 的选举与脑裂防护。
  • kp-013/014/015/016:复制的语义层(一致性模型、quorum、冲突、会话保证)。
  • kp-019:Raft 本质是把"主从复制 + 选举 + 安全性"做成严密协议。

自测题

  1. 同步、异步、半同步复制各自的取舍?

答:同步=一致性最强但任一副本故障阻塞写;异步=低延迟高可用但 failover 丢数据;半同步=至少一个确认,兼顾二者,最常用。

  1. "读己之写"异常如何产生、如何修?

答:写主后读落到未同步的从库;修法是会话内粘住主库或带复制位点检查的从库选择。

  1. 无主复制为什么需要读多个副本?

答:写只到达部分副本且并发写各自可见,单副本读到的不保证最新;读多数并比较版本才能以高概率取到最新值。

延伸阅读

  • Martin Kleppmann《DDIA》第 5 章。
  • DeCandia 等, "Dynamo: Amazon's Highly Available Key-value Store"(SOSP 2007)。
  • MySQL/PostgreSQL 官方复制文档(同步/半同步语义)。