DDIA 导读 CH 6 — 复制 (上)

2026年10月12日

💎 加入 E+ 會員方案 與超過千位工程師一同在社群成長,並獲得更多深度的軟體前後端學習資源

在《Designing Data-Intensive Applications》的第六章中,作者开始讨论“数据复制(Replication)”这个数据密集型应用系统中的核心概念。数据复制是指,一份数据不只存放在一台机器上,而是让多台机器同时保有相同数据的副本。

直观来看,把相同的数据复制到多台机器上,会占用更多存储空间;在实际部署中,需要花更多钱购买或租用机器,带来额外成本。既然如此,为什么要做复制?它有哪些好处?实际执行复制时有哪些难点?不同复制方式又有哪些技术权衡?作者在这一章中逐一讨论了这些问题。

为什么需要复制?

虽然复制会带来成本,但也能为系统带来不少好处。书中提到了三个主要好处。

第一个是通过复制,让数据离用户更近,从而加快读取速度。例如,如果用户在台湾,但数据库在美国,每次用户浏览网页,请求都要跨越太平洋到美国的数据中心,再把结果传回来。这段距离会带来额外延迟。

这类延迟可以通过在距离用户较近的地区部署读取副本来降低。只要部分读取请求可以由当地副本处理,台湾用户就不必每次都跨越太平洋访问美国的数据中心。不过在单主节点架构中,写入通常仍须发送到主节点,因此地理副本主要改善的是可由副本处理的读取延迟。

第二个是提高系统可用性。如果所有数据都存放在一台机器上,当这台机器故障时,很可能导致服务不可用。但如果有多个副本,其中一台机器出现故障,其他机器就能接替它,让系统继续运行。换句话说,复制能降低单点故障带来的影响。

第三个是增加读取吞吐量。许多系统都是读多写少(例如,大多数人使用电商网站时,浏览商品的次数多于下单次数;使用社交媒体时,阅读帖子的次数多于发帖次数)。如果所有读取都由一台机器处理,系统能承载的流量最终会达到单台机器的上限。如果有多个副本,就能把请求分散到不同机器上,让每台机器分担部分流量,从而提高整体读取吞吐量。

单主节点复制(single-leader replication)

讨论复制时,不可避免地要思考“如何确保所有数据最终都会出现在所有副本上”。每次数据库发生写入,每个副本都需要更新,否则就会出现数据不一致。单主节点复制是解决这个问题最常见的方法。

顾名思义,单主节点复制由一台主要机器负责接受写入,其他机器跟随它同步。在这种模式下,有两个角色:领导者(主数据库,也称主节点)和跟随者(副本数据库,也称从节点)。所有客户端写入都会发送到主节点,再由主节点把变更传递给副本。副本不接受客户端直接写入;至于是否处理读取请求,则取决于数据库的能力和系统配置。

!image.png

只有一个主数据库,搭配多个副本数据库,写入入口会比较明确,整体维护也比较简单。因此,许多数据库(包括 PostgreSQL、MySQL、MongoDB 等)都会选择单主节点复制作为默认复制方式。

同步与异步的权衡

虽然单主节点复制是相对简单的架构,但实际运行中仍有许多技术难题需要克服。

第一个问题是如何定义“写入成功”。例如,用户在社交媒体网站上更新头像,写入请求发送到主数据库。主数据库收到请求并保存新头像数据后,会把变更发送到各个副本数据库。这时,主数据库应该直接返回成功,还是等副本数据库确认收到后才返回成功?

下图展示了几种时间线。一种选择是主数据库完成数据变更后直接返回 ok,所有副本的更新都以异步方式进行。另一种是等一个副本数据库(例如 副本 1)完成更新后就返回 ok,这属于同步与异步混合的方式(副本 1 是同步的,其他副本则是异步的)。最后一种是等所有副本都更新完成后才返回 ok,也就是全部同步。

!image.png

大家觉得这三种方式分别需要面对哪些权衡?

同步复制可以降低已确认写入在主节点故障后丢失的风险。根据配置不同,系统可能保证至少一个、指定数量,或所有同步副本已收到并持久化该变更。对于特别重视数据安全的系统,例如金融和库存系统,这种做法能带来更多保障。但同步复制一个明显的问题是,用户需要等待更长时间才能收到 ok。此外,如果某个副本数据库出现问题,主数据库等待它返回 ok 的时间可能更长。这种阻塞对用户体验很不理想。

异步复制则相反:主数据库可以很快向客户端返回 ok,用户体验会更好一些。但由于没有确保副本数据库已成功更新,数据丢失的风险也更大。在一些重视低延迟、能够接受部分数据丢失的场景中,这种做法很常见,例如事件分析日志系统或浏览记录跟踪系统。在这些场景下,丢失一小段数据不一定会造成事故。

同步与异步混合则是一种比较平衡的做法。例如,只要一个副本数据库返回 ok,就直接向用户返回 ok。这样能确保至少另一个节点已保存该变更,同时不用等所有副本都返回 ok,延迟也会低一些。

实际使用时,需要根据系统特性做选择。有时混合方案会在多数副本完成同步后返回 ok(例如,总共有 5 个副本,其中 3 个已更新成功)。如何选择没有标准答案,更多取决于系统更需要数据安全,还是更看重低延迟。

新增副本的难点

单主节点复制除了需要选择同步或异步复制,还需要面对新增副本的难点。

加入新副本,并不是增加一台机器,再把主数据库的数据全部导入就行,因为大多数主数据库的数据一直在变化。客户端会不断向主数据库写入新数据。这就像去复印店复印一本书:原本有十章,复印完后却发现又新增了两章,复印结果自然就不完整了。

面对这个问题,直观的解决办法是锁住主数据库,暂时不接受新的写入,但这样会降低系统可用性。事实上,目前仍有许多系统采用这种做法,在建立初始副本时暂停写入,以换取更简单、容易验证的一致性流程;代价是服务在这段时间内无法正常接受写入。

如果想在不停机的情况下完成这件事,书中提到可以通过获取主数据库的“快照”来实现。具体来说,先把快照复制到新的副本数据库,让副本获得主数据库在某个时间点的状态。接着,找出快照时间点之后的所有变更(PostgreSQL 可以使用日志序列号,MySQL 则可以使用 binlog 或 GTID),补上快照建立后尚未应用的变更,让副本完整追上主数据库的更新进度。

副本数据库的成本

开头提到,多个副本会增加运维成本。在现代云架构中,租用高性能磁盘或内存的费用并不低。如果数据库中有很多不常用的数据,把它们存放在使用昂贵磁盘和内存的多个副本中,就像租用黄金地段的办公室来堆放纸箱一样,从成本角度看不太合理。

因此,业界逐渐出现了搭配对象存储服务(例如 AWS S3 或 Google Cloud Storage)的做法。这类服务比云数据库便宜许多,同时提供很好的持久性和跨区域复制能力(例如,使用 S3 可以很方便地自动跨区域复制数据)。

虽然对象存储便宜又方便,但与磁盘相比,访问延迟高得多,也就是说速度慢得多。因此,实际使用对象存储时,通常会采用分层架构:经常访问的数据放在磁盘或内存中,较少访问的数据则放在对象存储中。

近期还有一个新概念叫无磁盘架构:不把本地磁盘作为持久化方案,需要持久保存的数据都存放在对象存储中,本地磁盘或内存只作为缓存。这样,主数据库节点就接近无状态;即使节点故障,也不必担心持久化数据随之丢失,因为数据仍然保存在对象存储中。当然,这种做法也有代价:如果主数据库故障,恢复时从对象存储获取数据会遇到更高的访问延迟。

后半篇将进一步讨论“复制时的延迟问题该如何处理?”。当副本还没跟上主节点的更新时,用户可能读到旧数据,应用程序该如何应对?这部分内容仅供 E+ 会员 阅读。


加入 E+ 会员方案

如果你觉得这篇分享有帮助,想阅读更深入的内容,欢迎加入 E+ 会员。除了有会员专属的深度文与影片,探讨前后端开发、AI 工程、职业发展,同时有 Discord 社群一同交流与成长。

许多 E+ 会员通过所任职公司的教育训练补助 (E+ 会开立有统编的发票),不用花自己的钱,也能每周通过深度主题文与社群交流,拓展技术视野、加速职业成长。

🧵 如果你想收到最即時的內容更新,可以在 Facebook、Instagram 上追蹤我們