分布式系统三大支柱:CAP、一致性协议、分布式事务

2 阅读12分钟

这是分布式系统的"理论物理"。理解了这三块,再看 ZooKeeper、etcd、TCC、Saga、Kafka 副本机制都能从根上看穿。

一、为什么需要分布式系统?

单机的极限:

  • 性能:单机 CPU、内存、磁盘有上限
  • 容量:TB 级数据单机放不下
  • 可用性:单机宕机 = 服务全挂
  • 地域:跨地域用户要就近访问

分布式系统就是用一群机器协作干活,但代价是引入了新难题:节点会失败、网络会延迟和分区、时钟不同步、消息会重复或乱序。

Leslie Lamport 名言:分布式系统就是当一台你不知道存在的机器宕机时,让你的电脑也无法使用的系统。


二、CAP 定理:分布式系统的"测不准原理"

2.1 三个属性

  • C (Consistency) 一致性:所有节点同一时刻看到相同数据(强一致 / 线性一致)
  • A (Availability) 可用性:每个请求都能在合理时间内返回结果(不一定是最新的)
  • P (Partition tolerance) 分区容错性:网络分区发生时系统仍能继续运行

2.2 核心命题

分区发生时,C 和 A 只能二选一。

关键澄清:CAP 不是"三选二",而是 P 是必选项(网络一定会出问题),剩下在 CPAP 之间二选一。

2.3 CP 和 AP 的实例

网络分区:节点 AB 失联

CP 选择(牺牲可用性):
  ZooKeeper、etcd、HBase、Redis Cluster(写)
  → 少数派分区拒绝服务,宁可不响应也要保证一致

AP 选择(牺牲一致性):
  Eureka、Cassandra、DynamoDB、CouchDB
  → 两边都继续响应,事后再合并冲突

2.4 CAP 的常见误解

  1. "CAP 三选二":不准确,P 必选
  2. "CP 系统不可用":只是分区时不可用,正常时高可用
  3. "AP 系统数据不一致":是最终一致,不是永久不一致
  4. "MySQL 是 CA":单机谈 CAP 没意义

2.5 BASE 理论:AP 的工程化

  • Basically Available:基本可用(降级、限流)
  • Soft state:软状态(允许中间状态)
  • Eventual consistency:最终一致

互联网业界主流:电商、社交、内容都是 AP + BASE,因为可用性 = 钱。

2.6 PACELC:CAP 的扩展

如果有分区(P),在 A 和 C 之间选;否则(E)在延迟(L)和一致性(C)之间选。

更贴近现实:没分区时,强一致也要付出延迟代价(要等多副本确认)。

系统模式
ZooKeeper、HBasePC + EC(始终强一致)
Cassandra、DynamoDBPA + EL(始终低延迟)
MongoDBPA + EC(分区时可用,平时一致)

三、一致性模型:从强到弱的光谱

强 ←─────────────────────────────────────→ 弱

线性一致性 → 顺序一致性 → 因果一致性 → 最终一致性
(Linearizable) (Sequential)  (Causal)    (Eventual)

3.1 线性一致性(Linearizability)

最强:所有操作看起来像在某个全局时刻瞬间发生,且符合实际时间顺序。

T1 写 x=110:00:00)→ T2 在 10:00:01 读 x,必须读到 1

代价:必须牺牲性能(要全网同步)。ZooKeeper、etcd 提供线性一致

3.2 顺序一致性

所有进程看到相同的操作顺序,但不要求和真实时间一致。

T1: 10:00 写 x=1
T2: 10:01 写 y=2
所有节点要么先看到 x=1 再 y=2,要么相反,但顺序统一

3.3 因果一致性

有因果关系的操作顺序一致;并发操作不要求顺序。

A 发了帖子 → B 评论了 A 的帖子
所有节点必须先看到帖子,再看到评论(因果)
但 C 同时发的另一个帖子和这俩没关系

3.4 最终一致性

只要不再有写入,最终所有副本会一致。中间过程可以混乱。DNS、邮件系统、电商商品详情都是这种。

3.5 工程取舍

业务一致性级别
银行转账、库存扣减线性一致
订单状态、支付因果或线性
商品详情、用户资料最终一致
点赞数、阅读量最终一致(可丢失)

四、共识算法:怎么让一群机器达成一致?

4.1 问题定义

N 个节点中,可能有节点宕机或网络抖动,怎么让大家就某个值达成一致

4.2 Paxos(1989,Lamport)

理论之祖。难懂出名(Lamport 自己用希腊议会比喻才讲清楚)。

核心:

  • Proposer:提案者
  • Acceptor:接受者
  • Learner:学习者
  • 两阶段:Prepare → Accept

实际很少直接实现,但 Chubby、Spanner、Zab、Raft 都源自 Paxos 思想。

4.3 Raft(2014,斯坦福)⭐

可理解性设计,是工程界的事实标准(etcd、Consul、TiKV、CockroachDB 都用 Raft)。

三种角色

       ┌──→ Leader(唯一):处理所有写
       │      ↓ 复制日志
   选举 │   Follower:复制 Leader 日志
       │      ↑ 选举超时变 Candidate
       └─── Candidate:拉票中

核心机制

1. Leader 选举

  • 每个节点有 election timeout(150-300ms 随机)
  • 超时未收到 Leader 心跳 → 变 Candidate → 给自己投票 → 拉票
  • 获过半票 → 成为 Leader
  • 任期(Term)单调递增,避免脑裂

2. 日志复制

客户端 → Leader 写日志(uncommitted)
        → 同步给 Follower
        → 过半 Follower ACK
        → Leader 提交(committed)
        → 返回客户端成功
        → 通知 Follower 也提交

3. 安全性约束

  • 只有日志最新的节点才能当 Leader(Election Restriction)
  • Leader 只能提交当前任期的日志(避免回滚已提交日志)

为什么需要奇数节点?

节点数容错(多数派)
31(多数派 = 2)
41(多数派 = 3,4 比 3 没收益)
52
73

3 节点容忍 1 故障,5 节点容忍 2 故障。增加节点提升容错但拖慢性能(要等更多 ACK)。生产环境通常 3 或 5

4.4 ZAB(ZooKeeper Atomic Broadcast)

ZooKeeper 用的协议,思想接近 Raft,但出现更早。差异:

  • 更强调原子广播
  • Leader 选举后有"恢复阶段"同步数据

4.5 Multi-Paxos / Fast Paxos / EPaxos

Paxos 的各种变体,进一步减少消息往返。EPaxos 无 Leader,多点写入,理论很美但工程实现极少。

4.6 拜占庭容错(BFT)

上面这些算法只解决节点宕机或网络问题,不解决节点撒谎(恶意节点)。

拜占庭问题:节点可能发送虚假消息。需要 BFT 算法(如 PBFT、HotStuff),代价高(节点数 ≥ 3f+1,f 是恶意节点数)。

应用:区块链。普通分布式系统假设节点是诚实的,不需要 BFT。


五、分布式事务:跨服务、跨库的 ACID

5.1 问题场景

下单 = 扣库存(库存服务) + 减余额(支付服务) + 创建订单(订单服务)
要么都成功,要么都失败

单库事务靠数据库,跨库就需要分布式事务方案。

5.2 2PC(两阶段提交)

流程

阶段 1:Prepare
  协调者 → 所有参与者:你能提交吗?
  参与者:执行事务但不提交,返回 yes/no

阶段 2:Commit
  全 yes → 协调者 → 所有参与者:提交
  有 no → 协调者 → 所有参与者:回滚

致命缺陷

  1. 同步阻塞:参与者锁资源等协调者命令,期间不能服务
  2. 协调者单点:协调者宕机 → 参与者一直等
  3. 数据不一致:阶段 2 部分参与者收到 commit,部分没收到
  4. 性能极差:两次网络往返 + 锁

现状:仅 XA 协议在传统数据库(MySQL XA、Oracle XA)中保留,互联网业界基本不用

5.3 3PC(三阶段提交)

引入 CanCommit → PreCommit → DoCommit 三阶段,加入超时机制。理论上改进 2PC 阻塞问题,实际仍有不一致风险,工程几乎不用。

5.4 TCC(Try-Confirm-Cancel)⭐ 互联网常用

业务侵入式的柔性事务

Try:预留资源(如冻结库存)
Confirm:确认提交(实际扣减)
Cancel:失败回滚(解冻)

实例:转账 100 元

# Try 阶段
account_a.freeze(100)    # A 账户冻结 100
account_b.freeze_in(100) # B 账户预增 100

# 全部 Try 成功 → Confirm
account_a.commit()       # A 真正扣 100
account_b.commit()       # B 真正加 100

# 任一 Try 失败 → Cancel
account_a.unfreeze(100)  # A 解冻
account_b.cancel_in(100) # B 取消预增

三大难题

  1. 空回滚:Try 没执行,Cancel 先到(网络乱序)→ 必须保证幂等且能识别"无 Try 的 Cancel"
  2. 悬挂:Cancel 先到,Try 后到 → 资源被永久占用
  3. 幂等:网络重试 → 同一个操作可能调多次

业界框架:Seata(TCC 模式)、ByteTCC、Hmily

5.5 Saga 模式 ⭐ 长事务首选

把长事务拆成一系列本地事务 + 补偿动作

T1 → T2 → T3 → T4
失败时反向补偿:C3 → C2 → C1

实例:旅游下单

预订机票 → 预订酒店 → 预订租车 → 支付
   ↓失败时
取消机票 ← 取消酒店 ← 取消租车

两种实现

  • 编排式(Choreography):每个服务监听消息,自己决定下一步。简单但难追踪
  • 协调式(Orchestration):中心化协调器调度。清晰但有单点

适用场景

  • 跨多个服务的长流程(订单、退款、流程审批)
  • 不要求强一致,能接受中间状态
  • 业界最主流的分布式事务方案

5.6 本地消息表 / 事务消息

核心思想:把"发消息"和"业务操作"放进同一个本地事务。

BEGIN;
UPDATE inventory SET stock = stock - 1;
INSERT INTO outbox (event) VALUES ('stock_deducted');
COMMIT;

-- 后台任务扫 outbox,发到 MQ,确认后删除

优点:简单、可靠、不依赖外部组件 缺点:消息有延迟

RocketMQ 的事务消息是该模式的产品化封装。

5.7 最终一致性 + 对账

最朴素的方案:

  • 业务操作记日志
  • 后台定时对账(小时级、天级)
  • 不一致就修复

适合低频、可容忍延迟的场景(如月底结算)。

5.8 选型决策

方案一致性性能复杂度适用
2PC/XA极差银行、强一致老系统
TCC金融、支付
Saga最终长流程、跨服务
本地消息表最终通用
事务消息最终RocketMQ 生态
对账最终极好低频对账

互联网业界事实标准Saga + 本地消息表 + 对账兜底


六、其他基石概念速览

6.1 幂等性(Idempotence)

多次调用和一次调用效果相同。

分布式系统的生存技能:网络抖动、重试、消息重复都靠幂等扛。

实现手段:

  • 唯一 ID(订单号、消息 ID)+ 数据库唯一索引
  • 状态机(已支付的订单不能再支付)
  • 乐观锁(version 字段)
  • Token 机制(一次性令牌)

6.2 分布式 ID

需求:全局唯一、趋势递增、高性能、信息安全。

方案优点缺点
UUID简单无序、太长
DB 自增有序性能差、单点
号段模式高性能不连续
雪花算法 (Snowflake)有序、高性能时钟回拨问题
Redis incr简单高性能依赖 Redis
Leaf(美团)/ TinyID工业级部署复杂

雪花算法

0 | 41 位时间戳 | 10 位机器 ID | 12 位序列号
   = 69     = 1024     = 单机 4096/ms

时钟回拨解决:等待 / 用上次时间 + 1 / 抛异常切机器

6.3 一致性哈希

普通哈希(key % N):节点变化时全部数据要迁移

一致性哈希:

  • 把节点和 key 都哈希到 0~2^32 的环上
  • key 顺时针找到的第一个节点 = 归属节点
  • 加节点:只影响相邻一段
  • 虚拟节点:避免数据倾斜

应用:Redis Cluster(用槽位变种)、Cassandra、CDN、负载均衡

6.4 Gossip 协议

去中心化的最终一致性传播协议(像八卦一样扩散)。

节点定期随机选几个邻居,互换状态
经过 O(log N) 轮,全网收敛

应用:Cassandra 节点发现、Redis Cluster 节点状态、Consul、区块链

6.5 向量时钟(Vector Clock)

解决"分布式系统中事件的因果关系"。每个节点维护一个版本向量 [A:1, B:3, C:2],更新时本地版本 +1,合并时取最大值。Dynamo、Cassandra 用来检测并发写入冲突

6.6 脑裂(Split Brain)

网络分区导致集群分成两半,各自选了 Leader。两个 Leader 同时接受写 → 数据冲突。

解决

  • 多数派原则(Quorum):少于过半的分区拒绝服务(Raft、ZK)
  • fencing:旧 Leader 被新 Leader fence(如让旧 Leader 写共享存储失败)
  • 优先级:预设主备优先级

七、CAP × 一致性 × 共识 × 事务 全景图

现实约束:
  ┌──────────────────────────────┐
  │ 网络分区不可避免(P 必选)       │
  └──────────────┬───────────────┘
                 │
          ┌──────┴──────┐
          │             │
       选 CP          选 AP
       (etcd/ZK)      (Cassandra)
          │             │
   ┌──────▼──────┐  ┌───▼──────────┐
   │ 共识协议      │  │ Gossip + 向量时钟 │
   │ Raft/Paxos  │  │ 最终一致         │
   └──────┬──────┘  └──────┬──────────┘
          │                │
   ┌──────▼──────┐   ┌─────▼──────┐
   │ 强一致存储    │   │ 高可用存储   │
   │ 选主、配置    │   │ 海量数据     │
   └─────────────┘   └────────────┘

跨服务业务(事务层):
  ┌──────────────────────────────────────┐
  │ Saga / TCC / 本地消息表 / 事务消息      │
  │  → 牺牲强一致换可用性,业务层补一致     │
  └──────────────────────────────────────┘

八、面试高频题速记

  • Q:CAP 三选二对吗? A:不准确,P 必选,实际是 CP/AP 二选一
  • Q:CAP 和 BASE 关系? A:BASE 是 AP 的工程化(基本可用 + 软状态 + 最终一致)
  • Q:Raft 怎么选主? A:随机超时变 Candidate,拉票获过半成 Leader,Term 单调递增防脑裂
  • Q:为什么是奇数节点? A:偶数容错没收益(4 和 3 一样只容忍 1 故障)
  • Q:脑裂怎么解决? A:多数派原则,少数派分区不工作
  • Q:2PC 为什么不用? A:同步阻塞、协调者单点、性能差、不彻底解决一致性
  • Q:TCC 三大坑? A:空回滚、悬挂、幂等
  • Q:Saga 和 TCC 区别? A:Saga 用补偿(事后回滚),TCC 用预留资源(事前冻结);Saga 适合长流程,TCC 适合短强一致
  • Q:本地消息表怎么保证一致? A:业务和消息记录在同一本地事务,后台扫描发送
  • Q:雪花算法时钟回拨怎么处理? A:等待回到原时间 / 用最后时间 +1 / 切机器 ID
  • Q:一致性哈希解决什么? A:节点变化时大部分数据不需要迁移
  • Q:Paxos 和 Raft 区别? A:等价但 Raft 更易懂(强 Leader、明确分阶段)
  • Q:拜占庭和非拜占庭区别? A:是否假设节点诚实,区块链需要 BFT