跳到正文
主页
返回

DDIA 第 6 章读书笔记:复制

为什么要复制

复制(replication)是在通过网络连接的多台机器上保留相同数据的副本,目的有三个:

复制的难点不在于”复制”本身,而在于处理数据的变更。本章讨论三类复制算法:单主复制、多主复制、无主复制,几乎所有分布式数据库都采用其中一种。

单主复制

最经典的复制方式,也称主备复制(primary-backup):

  1. 一个副本被指定为领导者(leader / primary),所有写请求必须发给它
  2. 领导者把数据变更以复制日志 / 变更流的形式发给追随者(follower / read replica),追随者按相同顺序应用写入
  3. 读请求可以发往领导者或任意追随者(追随者对客户端只读)

PostgreSQL、MySQL、Oracle Data Guard、MongoDB、Kafka,以及 Raft 系系统(CockroachDB、TiDB、etcd)都采用此模型。

同步与异步

故障处理

复制日志的三种实现

方式原理问题
基于语句转发 INSERT/UPDATE/DELETE 语句NOW()、RAND() 等非确定性函数在各副本结果不同
WAL 传输直接发送预写日志(磁盘块级)与存储引擎耦合,主从版本必须一致,无法滚动升级
逻辑(行)日志以行粒度记录增删改(如 MySQL binlog)与存储引擎解耦,可跨版本,可做 CDC 供外部系统消费

复制延迟引发的一致性问题

异步复制的追随者可能返回陈旧数据。停止写入后副本最终会赶上,这叫最终一致性——但”最终”没有上限。针对三类典型异常,有三种一致性保证:

写后读一致性(read-your-writes)

保证:用户重新加载页面时,总能看到自己刚提交的更新(不保证看到其他人的)。

实现方法:

单调读(monotonic reads)

保证:用户一旦读到较新的数据,之后不会再读到更旧的——不会”时光倒流”。

异常场景:用户刷新页面,两次查询被路由到复制延迟不同的副本,先看到评论后又消失了。

实现:让每个用户固定读同一个副本(如按用户 ID 哈希选副本),副本失效时再切换。

一致前缀读(consistent prefix reads)

保证:一系列按顺序发生的写入,被读取时也按相同顺序出现——回答不能出现在问题之前。

这在分片数据库中尤其成问题:不同分片独立运行、无全局写入顺序,写入可能以错误顺序抵达副本。解决办法是让有因果关系的写入落在同一分片,或用算法显式跟踪因果依赖。

多主复制

单主的痛点是所有写入必须经过一个领导者,连不上就写不了。多主复制(主动/主动)允许多个节点接受写入,每个领导者同时是其他领导者的追随者。有意义的场景是异步多主——同步多主本质上等价于单主。

适用场景

复制拓扑

写入冲突的处理

多主最大的难题:两个领导者并发写同一记录,复制时才发现冲突。四种策略:

  1. 冲突避免:确保同一记录的写入都经过同一领导者(如按用户归属地区路由)
  2. 最后写入者胜(LWW):按时间戳取最新值,简单但会丢写入(Cassandra 默认)
  3. 手动解决:保存所有并发值(兄弟值),交应用或用户合并(CouchDB)
  4. 自动解决:CRDT 等算法自动合并并发写入,保证所有节点收敛到相同状态

无主复制

彻底放弃领导者:客户端(或协调者)直接把写入并行发给多个副本,读也并行读多个副本。Dynamo 风格数据库:Riak、Cassandra、ScyllaDB(注意 DynamoDB 实为 Multi-Paxos 单主,架构完全不同)。

追赶错过的写入

读写仲裁

n 个副本中,写需 w 个确认、读需查 r 个节点。只要 w + r > n,读到的 r 个节点中必有一个持最新值。常见配置 n=3/5、w=r=(n+1)/2,可容忍 (n-1)/2 个节点失效。

但仲裁不是绝对保证——并发读写、部分写入成功、时钟偏移、再平衡期间都可能读到陈旧值。w、r 只是调节陈旧概率的旋钮。

性能特点

三种方式对比

单主多主无主
写入路径唯一领导者任一领导者并行写 w 个副本
一致性最强(可串行化)弱,需冲突解决弱,需冲突解决
故障切换需要,有脑裂风险不需要不需要
适用场景通用,最易理解跨地域、离线同步高容错、多地区
代表系统PostgreSQL、MySQL、MongoDBYugabyteDB、pglogicalCassandra、Riak、ScyllaDB

参考:DDIA 第 6 章 · 复制(中文翻译)


分享本文:

下一篇
Harness 工程(三):验证与测试设计