3. 数据复制与一致性:主从复制、多主复制、Quorum机制、最终一致性

数据复制,说白了就是把同一份数据在多个节点上各存一份。为什么要这么干?两个原因:一是怕单点故障,一台机器挂了数据不丢;二是让读请求分散到不同节点,提升吞吐量。

但复制带来的最大麻烦,就是一致性问题。你想想看,如果我在北京机房写了一个值,上海机房的副本还没同步完,用户读到的就是旧数据。这怎么搞?

这一章,我把几种主流的复制策略掰开揉碎讲清楚。每种策略我都踩过坑,咱们边聊边避。

3.1 主从复制(Single-Leader Replication)

这是最经典、也是最常用的复制模式。MySQL 的主从同步、Redis 的主从复制、PostgreSQL 的流复制,底层都是这个思路。

核心逻辑很简单:

  • 选一个节点当 主节点(Leader),所有写请求必须走它。
  • 其他节点当 从节点(Follower),只处理读请求。
  • 主节点把写操作以日志或事件流的方式,推送给所有从节点。

一句话总结:写操作单点入口,读操作水平扩展。

我在项目中遇到过一个问题:某次大促活动,主库的 CPU 直接飙到 95%。一查,原来是业务方把一些统计查询也打到主库上了。我当时的处理方式——强制所有只读流量走从库,主库只保留写操作和实时性要求极高的读。效果立竿见影。

同步复制 vs 异步复制

对比维度 同步复制 异步复制
数据一致性 强一致(主从数据实时相同) 最终一致(有短暂延迟)
写性能 慢(需等待所有从库确认) 快(主库写完即返回)
主库故障影响 从库数据完整,可立即切换 可能丢失未同步的数据
典型场景 金融交易、账户余额 日志、社交动态、缓存

我个人习惯:核心交易链路用同步复制,非核心业务用异步复制。比如订单支付成功后必须同步到从库,否则用户刷新页面看到"未支付"会投诉;但用户的浏览历史晚几秒同步,没人察觉。

避坑指南:我曾经在异步复制场景下,主库宕机后丢失了 2000 条订单状态变更记录。后来我加了一个"半同步复制"的中间状态——主库至少等待一个从库确认,才返回客户端成功。这样既保证了性能,又降低了丢数据的风险。

3.2 多主复制(Multi-Leader Replication)

主从复制有个硬伤:所有写操作都压在一个节点上。如果业务量大到单机扛不住写,或者你需要多机房就近写入,多主复制就派上用场了。

多主复制,就是允许多个节点同时接受写请求。每个节点既是主库,也是其他节点的从库。

典型场景:

  • 多数据中心:北京、上海、深圳各部署一个主库,用户就近写入。
  • 离线客户端:比如协作编辑文档,每个客户端本地就是一个"主库",联网后合并冲突。
  • CouchDB、Cassandra 等数据库:天然支持多主架构。

但多主复制最大的坑,就是写冲突。你想想看,两个用户同时修改同一条记录,一个改成 A,一个改成 B,到底听谁的?

冲突处理策略

策略 做法 优缺点
最后写入者获胜(LWW) 以时间戳或版本号最大的为准 简单,但可能丢失数据
自定义合并逻辑 应用层编写合并函数 灵活,但开发成本高
CRDT(无冲突数据类型) 使用数学上可合并的数据结构 自动合并,但适用场景有限
事务性协调 引入分布式锁或事务管理器 强一致,但性能下降明显

我记得有一次做多机房部署,用了最后写入者获胜策略。结果因为两个机房的时钟偏差,导致旧数据覆盖了新数据。排查了整整一个下午。后来我强制所有节点使用 NTP 时间同步,并且把时间戳精度从毫秒级提升到微秒级,才彻底解决。

我的建议:如果业务允许,尽量用 CRDT 或应用层合并。LWW 虽然简单,但"丢数据"这个后果有时候比系统崩溃还严重。

3.3 Quorum 机制

Quorum 机制,是分布式系统中用来在可用性和一致性之间做权衡的经典方案。它不依赖固定的主从关系,而是通过"法定人数"来决策。

核心公式:

W + R > N
  • N:副本总数
  • W:写操作需要确认的节点数
  • R:读操作需要查询的节点数

只要满足 W + R > N,就能保证读操作至少能拿到一个包含最新数据的节点。

举个例子:N=3,W=2,R=2。写操作需要 2 个节点确认成功,读操作需要查询 2 个节点。因为 2+2=4 > 3,所以读操作一定能读到最新数据。

不同配置下的效果

配置 特点 适用场景
W=N, R=1 写强一致,读性能高 写少读多,对一致性要求高
W=1, R=N 写性能高,读强一致 写多读少,对一致性要求高
W=Quorum, R=Quorum 读写均衡,容忍部分节点故障 大多数通用场景
W=1, R=1 读写最快,但可能读到旧数据 对一致性无要求的场景

我在设计 Cassandra 数据模型时,就大量使用了 Quorum 机制。比如用户登录 session,我设置 W=2, R=2(N=3),这样即使一个节点挂了,读写仍然能正常进行。

注意:Quorum 机制并不能保证"强一致性",它只能保证"线性一致性"的一个弱化版本。如果你需要严格的 ACID 事务,Quorum 不够用,得引入 Paxos 或 Raft。

3.4 最终一致性

最终一致性,说白了就是:系统不保证你读到的数据是最新的,但保证只要没有新的写入,最终所有副本都会收敛到同一个值。

这是分布式系统里最常用的一致性模型。为什么?因为强一致性太贵了——每次写操作都要等所有节点确认,延迟高、可用性差。

最终一致性的几种变体:

  • 因果一致性:如果 A 操作在 B 操作之前发生,那么所有节点看到 A 的结果后,才能看到 B 的结果。
  • 读己之写:用户总能读到自己写入的数据。
  • 会话一致性:在同一个会话内,保证读己之写。
  • 单调读:如果用户读到某个值,后续不会读到更旧的值。

我举个例子你就明白了。你在微博发了一条动态,发完后刷新页面,有时候会看到"发送成功"的提示,但动态内容还没显示出来。过一两秒再刷新,就出来了。这就是最终一致性——写操作先返回成功,后台异步同步到所有副本。

核心原则:最终一致性不是"弱一致性",而是"有保证的收敛"。只要系统还在运行,数据最终会一致。

如何实现最终一致性?

  1. 异步复制:主库写完就返回,后台线程慢慢同步到从库。
  2. 反熵(Anti-Entropy):节点之间定期比对数据,修复不一致。
  3. 读修复(Read Repair):读操作发现某个副本数据旧,主动触发修复。
  4. Hinted Handoff:目标节点临时不可用时,由其他节点代为接收写操作,等目标恢复后再转发。

我个人习惯:在业务层面容忍最终一致性,但在基础设施层面尽量缩短不一致的时间窗口。比如把异步复制的延迟控制在 100ms 以内,这样用户基本感知不到。

3.5 知识结构总览

下面这张图,把本章的核心知识点串起来了。你可以把它当作一个"决策地图"——面对不同的业务场景,该选哪种复制策略。

数据复制与一致性决策地图 数据复制策略 主从复制 多主复制 Quorum 机制 同步复制(强一致) 异步复制(最终一致) 半同步复制(折中方案) LWW(最后写入者获胜) CRDT(无冲突合并) 应用层自定义合并 W+R > N 公式 读写权衡(W/R 调节) 节点故障容忍 最终一致性:所有策略的底层收敛保证 异步复制 / 反熵 / 读修复 / Hinted Handoff

3.6 如何选择?

没有银弹。我一般按下面这个思路来选:

  • 业务对一致性要求极高(钱、账户、订单):主从复制 + 同步模式,或者 Paxos/Raft 共识算法。
  • 业务需要多机房就近写入:多主复制 + CRDT 或应用层合并。
  • 业务对可用性要求极高,能容忍短暂不一致:Quorum 机制,W 和 R 调小一点。
  • 业务完全不在乎一致性(日志、点赞数、浏览记录):最终一致性 + 异步复制,怎么快怎么来。

一个小技巧:如果你不确定选哪个,先从主从复制 + 异步模式开始。这是最简单的方案,出问题概率最低。等业务量上来了,再根据瓶颈点逐步演进。

好了,这一章的内容就到这里。数据复制和一致性是分布式系统的核心课题,没有标准答案,只有 trade-off。你多踩几次坑,自然就找到感觉了。