#DevOps

分布式一致性:CAP、Raft 与共识

分布式系统绕不开一致性——CAP 怎么取舍、Raft 怎么达成共识、最终一致 vs 强一致怎么选。本文用大白话讲清 CAP 定理的真相(不是三选二)、Raft 选举与日志复制、以及实际系统(主从/Quorum/CRDT)怎么在不同一致性间取舍。

本文要点

  • 主题聚焦:DevOps
  • 适合人群:独立开发者、全栈工程师、AI 应用开发者
  • 核心价值:快速理解背景、实践路径、常见误区和工程建议
作者 @admin·发布于 2026年5月13日·更新于 2026年7月23日

分布式一致性:CAP、Raft 与共识

先抛一个经典线上事故:

机房之间光纤抖动,主库和备库之间的网络断了 30 秒。这 30 秒里,两台机器都认为自己是"主",各自接收了写入。等网络恢复,工程师面对的是两份互相冲突的数据——这就是教科书级的脑裂(split-brain)。

只要你做分布式,迟早会撞上这类问题。多台机器组成一个系统,网络会断、机器会挂、时钟会漂——在这种不可靠的底座上,怎么让数据"看起来像只有一份"?这就是**一致性(consistency)和共识(consensus)**要解决的事。

这篇文章讲透四件事:CAP 定理到底在说什么(以及大众怎么误读它)、一致性的强度谱系、Raft 怎么用大白话达成共识、以及工程上主从/Quorum/CRDT 到底怎么取舍。

一、一致性为什么绕不开

单机数据库不需要操心一致性——一份写在磁盘上,读出来就是它。但分布式系统天然是多副本:为了高可用(一台挂了另一台顶)、为了低延迟(数据就近部署)、为了读吞吐(多副本并行读)。

只要数据被复制到多台机器,问题就来了:

  • 写一台之后,另一台什么时候能读到?立刻,还是延迟几秒?
  • 两台同时收到不同的写,以谁为准?
  • 网络断了的时候,两边各自写,合起来怎么处理?

副本是分布式一切问题的根源。一致性,就是回答"多个副本在任意时刻,对外能不能表现得像一个整体"。

工程上不存在"完美的一致性"——那等于单机。我们要做的,是在"一致性强一点"和"系统可用一点 / 性能好一点"之间做取舍。而这个取舍的理论天花板,就是 CAP。

二、CAP 定理的真相:别再"三选二"了

坊间最常见的误读:"CAP 就是 consistency、availability、partition tolerance 三选二"。于是有人拍胸脯说"我们系统是 CA"。这话基本是错的。

CAP 的准确表述是:在网络发生分区(partition)的时候,你只能在一致性和可用性之间二选一。

  • C(Consistency):读到的一定是最新写入的。
  • A(Availability):每个请求都能收到(非错误的)响应,不保证是最新。
  • P(Partition Tolerance):网络允许丢消息、分区、延迟。

关键认知:P 不是可选的,是物理事实。网络一定会分区,交换机会抖、光纤会被挖断、机器网卡会过载。分布式系统不能假装 P 不存在。

所以真正的取舍是这样的:

text
没有发生分区(正常情况):C 和 A 可以同时要,大家开心。
发生分区(网络断了):   要么拒绝写(C,牺牲 A),
                       要么允许各写各的(A,牺牲 C)。

CAP 应该被理解为"分区时的一致性/可用性权衡",而不是"三个里挑两个"。不存在真正意义上的 CA 分布式系统——除非你是单机。

工程上:

  • 多数传统关系库分库中间件:分区时保 C,拒绝脑裂那一边的写。
  • Cassandra、Dynamo 这类系统:分区时保 A,先让写进去,事后靠向量时钟、读修复等手段最终一致。
  • 绝大多数现代系统(ZooKeeper、etcd、consul)走的是 CP 路线——共识协议保证强一致,牺牲少数派的可用性。

一句话:CAP 不是用来选型的,是用来理解系统在故障下的极限行为的。

三、一致性谱系:不是非黑即白

CAP 里的 C 是个二元概念,但真实世界的一致性是连续光谱,从强到弱大致四档:

强度通俗解释代表
线性一致(Linearizability)全局只有一份"当前值",读写像单机一样严格etcd、Spanner
顺序一致(Sequential)所有节点看到的操作顺序一致,但不一定与真实时间对应ZooKeeper 的写
因果一致(Causal)有因果关系的操作保序,无因果的可乱序评论系统、协作工具
最终一致(Eventual)停止写入后,副本最终会收敛到一致;中间随便乱DNS、Cassandra、S3

强度越高,实现代价越大(需要协调、加锁、跨节点 RPC),延迟越高。强度越弱,性能和可用性越好,但应用层要容忍"读到旧数据"。

工程判断:绝大多数互联网业务用最终一致就够了。只有少数场景——账户余额、库存扣减、分布式锁——才需要线性一致。别一上来就要求全局强一致,那是给自己挖坑。

四、Raft:讲人话的共识协议

线性一致怎么实现?靠共识算法:让一组机器对"下一条要写入什么"达成一致。Raft 是当下最主流的共识协议,etcd、Consul、TiKV、Redis Cluster 的 gossip、Kubernetes 的状态都靠它。

Raft 之所以取代 Paxos 成为教学和工程的事实标准,核心就一句话:它为了可理解性而设计。它把共识拆成三个清晰的子问题:选主、日志复制、安全性。

1. 选主(Leader Election)

Raft 集群里同一时刻只有一个 Leader,所有写请求都走它。选主流程:

text
节点启动 / Leader 失联(心跳超时)
        ↓
  term(任期)加 1,自己变成 Candidate
        ↓
  投自己一票,并行发 RequestVote 给其他节点
        ↓
  收到多数(N/2 + 1)票 → 当选 Leader
        ↓
  周期性发心跳维持权威,其他节点降为 Follower

关键设计:多数决(quorum)。不需要所有节点都同意,只要多数同意即可。一个 5 节点集群,挂 2 个还能正常选主;挂 3 个就选不出 Leader,集群停服——这就是 CP 系统的代价。

2. 日志复制(Log Replication)

Leader 收到写请求后,不是写完就返回,而是走一套复制流程:

text
客户端 → Leader 写本地日志(未提交)
              ↓
        Leader 并行复制到所有 Follower
              ↓
        多数 Follower 复制成功 → Leader 标记"已提交"
              ↓
        Leader 返回客户端成功,异步通知 Follower 提交

**为什么是多数,而不是全部?**因为"多数 + 多数"必然有交集——任意两个多数派至少有一个共同节点。这就保证了已提交的日志不会被覆盖(安全性)。如果等所有节点,那一个慢节点就会拖垮整个集群的写入。

3. 一个完整写请求的生命周期

text
Client → Leader:  SET x=5
Leader:  append 日志 [term=3, idx=7, SET x=5]
Leader → Followers:  AppendEntries RPC
Followers:  写入日志,回 OK
Leader:  收到 3/5 节点(含自己)确认 → 提交(commit)
Leader:  应用到状态机(x=5),返回 Client 成功
Leader → Followers:  下一次心跳带上 commitIndex,Follower 提交

这套机制保证了:已提交的日志一定不丢、所有节点最终一致、Leader 切换时数据连续。代价是每次写都要走一次跨节点 RPC,延迟比单机高一个数量级。这就是强一致的"价格标签"。

五、Paxos:祖师爷,知道它存在就行

Raft 之前,共识算法的事实标准是 Paxos(Lamport 1998)。它在数学上极优雅,证明完备,Google Chubby、Spanner 都基于它。问题只有一个:太难懂。论文里"准备阶段"、"接受阶段"、proposal number 那一套绕得云里雾里,连工程实现都各显神通(Multi-Paxos 没有标准实现)。

Raft 的作者(Diego Ongaro)就是受了 Paxos 的折磨,刻意设计了一个"功能上等价、但更好理解"的算法。所以工程上:理解 Raft 就够了,Paxos 留给读论文和面试装 X 用。看到 Paxos 字样,知道它是 Raft 的祖宗、理论更强但工程更难,这就足够了。

六、实际系统怎么取舍

理解了原理,看实战。三种典型的工程取舍,对应三种一致性强度:

1. 主从异步复制(最终一致,可用性优先)

MySQL 半同步、Redis 主从、ES 副本,基本都属于这一类。Leader 写本地立刻返回,异步把日志推给副本。

  • 优点:写延迟低,Leader 挂了虽然有少量数据丢失,但系统整体可用。
  • 风险:Leader 挂掉瞬间未同步的写会丢;异步期间读到旧数据。
  • 适用:日志、用户行为、缓存、搜索索引——能容忍秒级数据延迟的场景。

2. Quorum / NWR(可调一致性)

Dynamo、Cassandra、Riak 把选择权交给用户,核心是 NWR 模型:

  • N:副本总数
  • W:写要求多少副本成功
  • R:读要求多少副本响应

只要 W + R > N,读写一定有交集,就能保证强一致。典型 N=3:

  • W=2, R=2:强一致,延迟中等(主流配置)。
  • W=1, R=1:最终一致,性能最高。
  • W=3, R=1:写重读轻的场景。

Quorum 的精髓是"一致性可调"——同一套存储,不同业务用不同配置。这是非常工程化的设计。

3. CRDT(无冲突数据类型,最终一致无需协调)

CRDT(Conflict-free Replicated Data Type)是另一条路:数据结构本身的设计保证,无论以什么顺序合并,最终结果都一致。

典型应用:协作文档(在线文档、Notion)、Yjs、RedisCRDTs、Riak Map/Set。你和人同时改一段文字,本地直接改,后台合并,不冲突——因为数据结构(比如 G-Counter、LWW-Set)在数学上保证了收敛。

CRDT 的代价是表达能力受限(不是所有操作都能无冲突合并),但优势是完全没有协调成本,可用性拉满。协作类、社交类、IM 状态同步,首选 CRDT。

七、ACID vs BASE:两套哲学

数据库领域有 ACID(原子、一致、隔离、持久),强调事务的严格一致;分布式领域有 BASE(Basically Available、Soft state、Eventually consistent),强调放弃强一致换可用性和性能。

这俩不是对立的,而是不同层次的取舍:

  • 单库事务的 ACID,保证库内数据正确。
  • 跨库、跨服务的分布式事务(2PC、TCC),保证全局强一致——但又重又慢又容易错,生产里慎用。
  • BASE 思路:接受中间状态软一致,用补偿、幂等、消息最终对账。

工程上主流做法是 Saga + 幂等 + 消息队列:把一个长事务拆成本地事务 + 事件,各服务独立提交,失败靠补偿回滚。这套比 2PC 实用 10 倍,是当下微服务一致性的事实标准。

八、工程上别强求全局强一致

最后说点实战判断。一致性选型有个朴素原则:按业务价值选,不按技术洁癖选。

  • 钱、库存、权限:必须强一致(线性一致)。账户余额少了 1 块钱就是事故,这笔钱不能省,用 Raft / 数据库事务 / TCC。
  • 用户资料、商品信息:最终一致就够。延迟一两秒看到新头像,没人会投诉。
  • 社交动态、评论、点赞:最终一致 + CRDW-like 合并。用户感受不到一致性强弱,但系统能扛住巨大流量。
  • 计数、排行、统计:最终一致 + 异步聚合。准确性差个 0.1% 没人在乎,吞吐差一个数量级有人在乎。

全局强一致是奢侈品:每一次强一致写,都是一次跨节点协调、一次延迟放大、一次可用性妥协。把它用在真正需要的地方,其他地方大胆用最终一致。

小结

写到这里,几条要点浓缩一下:

  1. 副本是分布式问题的根源,一致性就是回答"多副本怎么表现得像一份"。这不是技术洁癖,是业务正确性的底线。
  2. CAP 的真相是"分区时的取舍",不是三选二。P 是物理事实,正常时 C 和 A 都可以要;分区发生那一刻,才需要选 CP 还是 AP。绝大多数存储系统走 CP。
  3. 一致性是连续光谱:线性一致最强最贵,最终一致最弱最快。别一上来就要全局强一致,按业务价值挑档位。
  4. Raft 三件事:选主、日志复制、多数决。理解"多数 + 多数必有交集"这一个点,就理解了它为什么既安全又能容忍少数派故障。Paxos 是祖宗,知道它存在即可。
  5. 选型看钱:钱的场景上强一致,社交和内容用最终一致 + CRDT / Saga,跨服务事务用"本地事务 + 消息 + 补偿"而非 2PC。

最后送一句金句:

分布式一致性的本质,不是消除冲突,而是诚实地面对冲突——在钱的面前强一致,在流量面前最终一致,在分区面前清醒地选 CP 还是 AP。强一致是奢侈品,不是日用品。

下一篇我们聊聊"分布式事务:Saga、TCC 与本地消息表",看看跨服务怎么把一致性这碗饭吃得更稳。

Related

相关推荐