- Published on
分布式系统完全指南 — 时钟、Consensus、Event Sourcing、Saga、CRDT、故障模式 (Season 2 Ep 12, 2025)
- Authors

- Name
- Youngju Kim
- @fjvbn20031
前言 — 分布式系统之所以难的真正原因
Butler Lampson 有一句著名的定义:
“分布式系统,就是你甚至不知道它存在的那台计算机出了故障,却让你自己的计算机没法用了。”
分布式系统之所以难,是因为下面这 3 件事会同时发生:
- 时钟不一致 (Clock Drift)
- 故障是局部的 (Partial Failure)
- 网络必须被怀疑 (Unreliable Network)
本文把针对这 3 个问题的理论工具(时钟、共识、一致性)与实战模式(Event Sourcing、Saga、CRDT)一次性梳理清楚。
第 1 部 — 8 Fallacies of Distributed Computing
Peter Deutsch 在 1994 年归纳的分布式系统 8 个错误假设:
- 网络是可靠的 ❌
- 延迟为零 ❌
- 带宽是无限的 ❌
- 网络是安全的 ❌
- 拓扑不会改变 ❌
- 管理员只有一位 ❌
- 传输成本为零 ❌
- 网络是同质的 ❌
任何违背这 8 条的假设,早晚都会在生产环境炸掉。
第 2 部 — 时间的 3 种模型
2.1 Physical Clock (物理时钟)
time.Now() — 操作系统的时钟。通过 NTP 同步。
问题:
- Clock Drift (ppm 级别的误差)
- NTP 跳变 (校时的时候会跳过去)
- Leap Second
- 数据中心之间有 ms 到几十 ms 的误差
2.2 Logical Clock (逻辑时钟)
Lamport Timestamp (1978):
事件发生时 counter++
消息发送: 发送 (event, counter)
消息接收: counter = max(local, received) + 1
优点: 保留因果关系 (happens-before) 缺点: 无法区分两者之间到底有没有因果关系 (concurrent vs causal)
2.3 Vector Clock
长度等于节点数 N 的向量:
A: [2, 1, 0]
B: [1, 3, 0]
- 可以判断 A 与 B 是并发的,还是谁在前
- 体积问题: 节点一多,元数据就变大
2.4 HLC (Hybrid Logical Clock, 2014)
物理时间 + 逻辑计数器 的组合:
HLC = (physical_time, logical_counter)
- 以物理时钟为基准排序 + 用逻辑时钟保留因果
- CockroachDB、YugabyteDB 等现代分布式 DB 的标准做法
2.5 TrueTime (Google Spanner)
用 GPS 与原子钟保证 ±7ms → 于是“这个时刻之后的事件一定在后面”成为可能。
- Commit Wait: 等过不确定区间(ms)之后再完成
- 强一致性 + 性能 (Linearizable)
- 普通企业实现不了 (需要硬件投入)
第 3 部 — 一致性模型的谱系
3.1 主要的一致性模型
由强到弱:
- Linearizable: 所有操作都按实时顺序呈现
- Sequential: 所有进程看到的顺序相同,但不是实时顺序
- Causal: 只对有因果关系的操作保证顺序
- Eventual: 最终会一致
3.2 重新审视 CAP
- Consistency: Linearizability
- Availability: 每个请求都能拿到回应
- Partition Tolerance: 网络分区时仍能工作
网络分区是躲不掉的 → 所以要在 CP 与 AP 之间选。
3.3 PACELC (更贴近现实)
- Partition: C or A
- Else (正常运行时): L(atency) or C(onsistency)
大多数系统是 PA/EL (分区时保可用 + 正常时保低延迟)。
3.4 2025 年 DB 的一致性选择
| DB | 模型 |
|---|---|
| PostgreSQL | 单节点 Serializable |
| MySQL | Read Committed (默认) |
| Spanner | Linearizable + External Consistency |
| CockroachDB | Serializable + Causal |
| Cassandra | Tunable (Quorum、Eventual) |
| DynamoDB | Eventual (可选 Strong) |
| Redis Cluster | 单键 Linearizable,多键 ❌ |
第 4 部 — Consensus: 共识算法
4.1 为什么需要 Consensus
分散的多个节点要对同一个值达成一致 — 领导者选举、状态复制、事务提交等等。
4.2 FLP Impossibility (1985)
在异步系统中,能容忍哪怕 1 个节点故障的 deterministic consensus 是不可能的。
真实系统要么依赖时序假设 (eventually synchronous),要么走概率性路线。
4.3 Paxos (1989)
Leslie Lamport 提出的鼻祖。正确,却以难懂难实现著称。
角色:
- Proposer (提出值)
- Acceptor (投票)
- Learner (学习决定)
问题: 实战中不好用,于是有了 Multi-Paxos、Fast Paxos、EPaxos 等变体。
4.4 Raft (2014)
在 Stanford 以“可理解的 Consensus”为目标设计。如今是事实标准。
3 个核心:
- Leader Election: 选出唯一的领导者
- Log Replication: 领导者把日志复制给 Follower
- Safety: 已 Committed 的日志不会被推翻
状态:
- Follower → Candidate (Election Timeout) → Leader (过半数投票)
- Leader 的 heartbeat 失败时 → 重新选举
使用场景: etcd, Consul, TiKV, CockroachDB, Kafka KRaft (2024 年起)。
4.5 PBFT (Practical Byzantine Fault Tolerance, 1999)
在存在恶意节点时,3f+1 个节点中可容忍 f 个故障。用于区块链。
阶段: Pre-prepare → Prepare → Commit。消息复杂度 O(N²)。
4.6 Tendermint、HotStuff (2018 年起)
把 BFT 按区块链的需要现代化。Libra/Diem 的 HotStuff,Cosmos 的 Tendermint。
4.7 Nakamoto Consensus
Bitcoin 的 Proof-of-Work。概率性共识 — 只要后面压了足够多的区块就算 confirmed。
缺点: 耗能、慢。所以 Ethereum 转向了 PoS。
第 5 部 — Replication
5.1 4 种复制模式
- Single-Leader: 写走领导者,读随处可读。简单。大多数 RDBMS。
- Multi-Leader: 多个领导者都接受写。需要冲突解决。用得很少。
- Leaderless: 所有节点对等。Dynamo-style。Cassandra, Riak, DynamoDB。
- Quorum-based: 只要 R + W > N 就有强一致性。
5.2 Replication Lag
Leader → Follower 的传播延迟会带来这些问题:
- Read-your-writes: 刚写完的东西读不到
- Monotonic reads: 之前看到过的东西又不见了
- Consistent Prefix: 看到了没有原因的结果
解决: 从 Leader 读、Sticky Session、基于 Timestamp 的一致性。
5.3 Conflict Resolution (Multi-Leader/Leaderless)
- LWW (Last Writer Wins): 简单,有丢数据的风险
- Version Vectors: 准确,复杂
- Application-level Merge: 用户自定义 (CRDT)
第 6 部 — CRDT (Conflict-free Replicated Data Type)
6.1 什么是 CRDT
一种在数学上不可能冲突的数据结构。无论按什么顺序合并,结果都一样。
6.2 两种类型
State-based (CvRDT): 传输状态本身 + merge Operation-based (CmRDT): 传输操作 + 重放
6.3 有代表性的 CRDT
| CRDT | 用途 |
|---|---|
| G-Counter | 只增 (计数器) |
| PN-Counter | 可增可减 |
| G-Set | 只能添加 |
| OR-Set | 添加与删除 |
| LWW-Register | 最后写入者胜出 |
| RGA | 有序列表 (文本编辑) |
| JSON CRDT (Automerge) | 嵌套结构 |
6.4 实战使用场景
- Redis CRDT (Enterprise): Active-Active Geo
- Riak: Dynamo + CRDT
- Automerge (Ink & Switch): 协作编辑器
- Yjs: Real-time collaboration (Notion、Linear 的灵感来源)
- Figma: 自研 CRDT 用于设计协作
6.5 局限
- 难以表达任意不变式 (例如唯一性)
- 元数据体积 (Tombstone)
- 会丢掉操作顺序的语义 (这既是优点也是缺点)
第 7 部 — Event Sourcing + CQRS
7.1 Event Sourcing
保存事件而不是状态。状态由重放事件推导出来。
events = [
AccountCreated(id=1, balance=0),
MoneyDeposited(account=1, amount=100),
MoneyWithdrawn(account=1, amount=30),
]
balance = fold(events, 0, apply) // 70
优点:
- 完整的历史 (审计与调试)
- 时间旅行
- 通过重放事件生成新的 View
- 对分布式友好 (事件是 append-only)
缺点:
- 复杂度 ↑
- 模式演进的问题 (如何解释旧事件)
- 没有 Snapshot 的话重放很慢
7.2 CQRS (Command Query Responsibility Segregation)
把写模型(Command) ≠ 读模型(Query) 分开。
Write: events → EventStore
↓
Projection
↓
Read: Materialized View (已优化)
优点: 读与写可以独立优化、独立扩展。 缺点: 一致性有延迟 (Eventual)。
7.3 Event Sourcing + CQRS 技术栈
- EventStoreDB: 专用 DB
- Kafka + Kafka Streams: 事件日志 + Projection
- Axon Framework (Java): 一体化
- MartenDB (.NET): 构建在 Postgres 之上
7.4 什么时候用,什么时候避开
适合的时候:
- 金融、交易这类可审计性很重要的场景
- 复杂业务逻辑 + 需要重建过去
- 以事件为中心的架构
该避开的时候:
- 简单 CRUD
- 团队里新手居多
- 一致性要求很简单
第 8 部 — Saga 模式
8.1 分布式事务的问题
2PC (Two-Phase Commit): 理论上完美,但会block,而且经不起 Coordinator 失败。如今几乎不用了。
替代方案: Saga — 一串本地事务 + 失败时执行补偿事务。
8.2 Choreography (协同)
各个服务发布 + 订阅事件:
OrderCreated → InventoryReserved → PaymentCharged → OrderConfirmed
↓ 失败
InventoryReleased → OrderCancelled (补偿)
优点: 服务之间的耦合度低。 缺点: 流程难以追踪。
8.3 Orchestration (编排)
由中央的 Orchestrator 显式管理流程:
Orchestrator:
1. ReserveInventory
2. ChargePayment
3. ShipOrder
失败时 ← CompensateAll
优点: 流程显式,调试容易。 缺点: Orchestrator 可能变成单点故障。
8.4 实现工具
- Temporal: 工作流引擎。Saga 的事实标准。
- AWS Step Functions: 托管服务
- Camunda: 基于 BPMN
- Cadence (Uber,Temporal 的前身): 开源
8.5 Temporal 示例
func ProcessOrder(ctx workflow.Context, order Order) error {
err := workflow.ExecuteActivity(ctx, ReserveInventory, order).Get(ctx, nil)
if err != nil { return err }
err = workflow.ExecuteActivity(ctx, ChargePayment, order).Get(ctx, nil)
if err != nil {
workflow.ExecuteActivity(ctx, ReleaseInventory, order)
return err
}
// ...
}
Temporal 免费提供了重启安全性、定时器与可视化。
第 9 部 — 故障模式 12 种
9.1 故障传播模式
- Cascading Failure: 一个服务变慢 → 调用方超时堆积 → 传播开来
- Thundering Herd: 缓存过期时所有请求一起涌向 Origin
- Retry Storm: 失败的请求被重试放大
- Metastable Failure: 临时性问题在恢复正常之后依然持续
- Split-Brain: 网络分区导致出现两个领导者
- Clock Skew Bug: 时钟差异让时间戳发生倒转
- Gray Failure: 没死,但功能已经劣化
- Poison Pill: 某条特定消息把所有 Consumer 都搞挂
- Queue Backlog: Consumer 太慢,队列无限堆积
- Hot Partition: 流量集中到某一个分片上
- Fan-out Overload: 一个请求变成 100 个内部请求
- Noisy Neighbor: 共享资源上一个租户影响所有人
9.2 防御模式
| 故障 | 防御 |
|---|---|
| Cascading | Circuit Breaker, Bulkhead, Timeout |
| Thundering Herd | Request Coalescing, Jittered refresh |
| Retry Storm | Exponential Backoff + Jitter |
| Metastable | Load Shedding, 限制 Queue Depth |
| Split-Brain | Fencing Token, Quorum |
| Clock Skew | HLC, 监控 NTP |
| Gray Failure | Health Check + 积极摘除 |
| Poison Pill | Dead Letter Queue + Alert |
| Queue Backlog | Backpressure, Auto-scale |
| Hot Partition | Consistent Hashing + replication |
| Fan-out | 考虑 Timeout 的层级深度, Async |
| Noisy Neighbor | Resource Quota, QoS |
9.3 Chaos Engineering
有意注入故障 → 验证系统。
- Chaos Monkey (Netflix 2011): 随机 kill 实例
- Chaos Mesh: K8s 原生
- LitmusChaos: 开源
- Gremlin: 商用
原则:
- 在生产环境做 (一开始先在预发)
- 限制 Blast Radius
- 自动回滚
- 定期执行 (Game Day)
第 10 部 — 分布式系统模式 10 条
- Idempotency: 同一个请求发 N 次 = 1 次的效果
- Exactly-once is a lie: At-least-once + Idempotent Consumer
- Outbox Pattern: DB 事务与事件发布的原子性
- Inbox Pattern: 幂等 Consumer 的实现方式
- Circuit Breaker: 检测失败 + 绕开
- Bulkhead: 用线程池隔离防止连锁失败
- Leader Election: 基于 Lease
- Sharding: 数据切分
- Sidecar: 把通用功能 (日志、安全) 放进独立容器
- Service Mesh: 把 Sidecar 提升成网络层
第 11 部 — 六个月分布式系统路线图
Month 1: 理论基础
- 精读 DDIA (Designing Data-Intensive Applications)
- Lamport 的两篇 Paper
- 8 Fallacies 思想实验
Month 2: Consensus 实操
- 亲手实现 Raft (MIT 6.824)
- 读 etcd、Consul 的内部实现
- 理解 Kafka KRaft 的结构
Month 3: Replication + Consistency
- 练习区分各种一致性模型
- CockroachDB、Spanner 的白皮书
- 实现 TrueTime 与 HLC
Month 4: Event Sourcing + Saga
- 用 Event Sourcing 做一个迷你银行系统
- 用 Temporal 实现 Saga
- 把 Outbox 模式真正落地
Month 5: CRDT + 实时
- 学习 Automerge、Yjs
- 做一个协作编辑器原型
- 精读 Figma 的技术博客
Month 6: 故障 + 运维
- 引入 Chaos Monkey
- 实现主要故障模式的防御
- 分析 10 份 Incident Postmortem
第 12 部 — 分布式系统检查清单 12 条
- 能说出 8 Fallacies
- 知道 Lamport vs Vector vs HLC 的区别
- 知道 CAP vs PACELC 的区别
- 能解释 Raft 的 3 个核心
- 知道 FLP Impossibility 的含义
- 知道 Single-Leader vs Leaderless 的取舍
- 知道 CRDT 要解决的问题
- 知道 Event Sourcing + CQRS 的优缺点
- 知道 Saga Choreography vs Orchestration 的区别
- 能说出 防御 Cascading Failure 的 3 种手段
- 知道 Outbox 与 Inbox 模式 的作用
- 知道 Chaos Engineering 的原则
第 13 部 — 分布式系统反模式 10 条
- “网络是可靠的”: 8 Fallacies 的第 1 条。Timeout 与重试是必须的
- 把 2PC 用到微服务里: 有 Block 的风险。改用 Saga
- 跨 Kafka 分区假定事件顺序: 只在分区内部才有保证
- 不用 DB 事务就想要 Exactly-once: 不可能。用 Outbox 或者幂等
- 信任 Clock: 用
time.Now()排序 → 出 bug。逻辑时钟是必须的 - 无限重试: 会引发 Retry Storm。要用 Backoff + Circuit Breaker
- 假定单节点 DB 加复制就安全了: 必须把 Split-Brain 考虑进去
- Consumer 失败时不 NACK 而是阻塞: DLQ 的设计是必须的
- 全局事务: 性能与可用性 ↓。重新设计边界
- “Eventual Consistency 最终会一致”: 从用户视角看仍然是问题。要用 UX 解决
结语 — 分布式系统是“脑子里的模型”
分布式系统是直觉最常背叛你的领域。“直觉很强”的工程师最危险。
需要的是:
- 怀疑之心: 网络、时钟、节点,统统都要怀疑
- 补偿之心: 做不到原子性,就设计补偿
- 重现之心: 用事件记录下来 → 随时可以重现
2025 年资深工程师的分布式能力,是把年薪拉开两倍的那根轴。这个领域有多难,就有多值钱。
下一篇预告 — “DB 完全指南: 内部结构、索引、查询规划器、分区、Vector DB”
Season 2 Ep 13 讲数据的心脏,也就是DB 进阶。下一篇包括:
- B-Tree、LSM-Tree、Hash Index 的内部结构
- 深入读懂查询规划器与 EXPLAIN
- 事务隔离级别 (Read Uncommitted 到 Serializable)
- 分片与分区策略
- PostgreSQL 的一枝独秀 (2025)
- Vector DB 与 pgvector、Qdrant、Weaviate
DB 究竟是怎么处理查询的,下一篇见。