分布式系统
概述
分布式系统是由多个通过网络协作的独立计算节点组成的系统。本文涵盖分布式系统的核心理论(CAP、一致性模型、共识算法)和工程实践(微服务、消息队列、分布式事务)。
1. CAP 定理
CAP 定理指出,分布式系统在面对网络分区(Partition) 时,不可能同时满足以下三个属性:
| 属性 | 含义 |
|---|---|
| C (Consistency) | 所有节点看到相同的数据 |
| A (Availability) | 每个请求都能收到(非错误的)响应 |
| P (Partition Tolerance) | 系统在网络分区时仍能运行 |
由于网络分区在实际中不可避免,实质上是在 CP 和 AP 之间选择:
| 选择 | 含义 | 代表系统 |
|---|---|---|
| CP | 保证一致性,牺牲可用性 | ZooKeeper, etcd, HBase |
| AP | 保证可用性,牺牲一致性 | Cassandra, DynamoDB, CouchDB |
PACELC 扩展
PACELC 定理进一步指出:即使没有分区(E=Else),也需要在延迟(Latency) 和一致性(Consistency) 之间权衡。
2. 一致性模型
2.1 强一致性(Strong/Linearizability)
所有操作表现为全局顺序执行,读操作总是返回最新的写入值。
\[
\text{write}(x, v) \prec \text{read}(x) \Rightarrow \text{read}(x) = v
\]
- 实现:单主节点 + 同步复制
- 代价:延迟高
- 代表:Spanner(TrueTime)
2.2 最终一致性(Eventual Consistency)
如果没有新的写入,最终所有副本将收敛到相同的值。
\[
\lim_{t \to \infty} \text{replicas}(x, t) \to \text{same value}
\]
- 窗口期内可能读到旧值
- 代表:DNS、Amazon S3、Cassandra
2.3 因果一致性(Causal Consistency)
保证因果相关的操作按因果顺序被所有节点观察到,但并发操作可以任意排序。
进程 A: write(x=1) → write(y=2)
进程 B: 如果看到 y=2,则必须已经看到 x=1
- 实现:向量时钟(Vector Clock)
- 比强一致性弱,比最终一致性强
3. 共识算法
共识问题:多个节点就某个值达成一致。
3.1 Paxos
Paxos 是最经典的共识算法,分为三个角色:
| 角色 | 职责 |
|---|---|
| Proposer | 提出提案(值) |
| Acceptor | 接受/拒绝提案 |
| Learner | 学习被选定的值 |
两阶段过程:
Phase 1 (Prepare):
Proposer → Acceptor: Prepare(n)
Acceptor → Proposer: Promise(n, accepted_value)
(Acceptor 承诺不再接受编号小于 n 的提案)
Phase 2 (Accept):
Proposer → Acceptor: Accept(n, value)
Acceptor → Proposer: Accepted(n, value)
(多数 Acceptor 接受 → 值被选定)
3.2 Raft
Raft 是对 Paxos 的简化,更易于理解和实现。
Leader 选举:
超时
Follower ───────────────→ Candidate
↑ │
│ 发现新 Leader │ 获得多数票
│ ▼
└──────────────────── Leader
↑ │
│ 发现更高任期
└────────┘ → Follower
选举规则:
- Follower 超时未收到心跳 → 转为 Candidate
- Candidate 增加任期号(Term),向所有节点请求投票
- 获得多数投票 → 成为 Leader
- 每个任期最多一个 Leader
日志复制:
Client → Leader: 写入请求
Leader: 追加到本地日志
Leader → Followers: AppendEntries RPC
Followers: 追加到本地日志,回复确认
Leader: 收到多数确认 → 提交(commit)
Leader → Client: 返回成功
Leader → Followers: 通知提交
一致性保证:
- 如果两个日志在某个索引处的任期号相同,则该索引之前的所有条目完全一致
- Leader 绝不会覆盖自己的日志
- 只有日志至少和多数节点一样新的 Candidate 才能赢得选举
4. 微服务架构
4.1 服务发现
┌──────────────┐
│ Service Registry │ ← 服务注册中心
│ (Consul/etcd) │
└──────┬───────┘
│ 注册/心跳/查询
┌────┼────┐
▼ ▼ ▼
[A] [B] [C] ← 微服务实例
| 方式 | 实现 | 代表 |
|---|---|---|
| 客户端发现 | 客户端直接查询注册中心 | Netflix Eureka |
| 服务端发现 | 负载均衡器查询注册中心 | AWS ALB, Consul |
| DNS 发现 | 通过 DNS 解析服务地址 | Kubernetes Service |
4.2 API Gateway
API 网关是微服务的统一入口:
Client → [API Gateway] → Service A
→ Service B
→ Service C
功能:
- 路由:根据 URL/Header 路由到不同服务
- 认证:统一的 JWT/OAuth 验证
- 限流:Rate Limiting
- 熔断:Circuit Breaker
- 日志:请求链路追踪
代表工具:Kong、Nginx、AWS API Gateway、Envoy。
4.3 熔断器模式(Circuit Breaker)
防止级联失败:
状态机:
失败率超过阈值
Closed ──────────────→ Open
↑ │
│ 探测成功 │ 超时
│ ▼
└──────────────── Half-Open
探测失败 → Open
class CircuitBreaker:
CLOSED, OPEN, HALF_OPEN = 0, 1, 2
def __init__(self, failure_threshold=5, timeout=30):
self.state = self.CLOSED
self.failures = 0
self.threshold = failure_threshold
self.timeout = timeout
self.last_failure_time = None
def call(self, func, *args):
if self.state == self.OPEN:
if time.time() - self.last_failure_time > self.timeout:
self.state = self.HALF_OPEN
else:
raise CircuitBreakerOpen()
try:
result = func(*args)
self._on_success()
return result
except Exception as e:
self._on_failure()
raise
5. 消息队列
5.1 Kafka
Apache Kafka 是高吞吐、持久化的分布式消息流平台。
核心概念:
| 概念 | 说明 |
|---|---|
| Topic | 消息分类 |
| Partition | Topic 的分区(并行单元) |
| Producer | 消息生产者 |
| Consumer | 消息消费者 |
| Consumer Group | 消费者组(一个分区只被组内一个消费者消费) |
| Offset | 消息在分区中的位置 |
| Broker | Kafka 服务器节点 |
Producer → Topic [P0|P1|P2] → Consumer Group
├── Consumer 1 ← P0
├── Consumer 2 ← P1
└── Consumer 3 ← P2
特点:
- 消息持久化到磁盘(顺序读写,高吞吐)
- 支持消息回溯(重新消费历史消息)
- 分区实现水平扩展
5.2 RabbitMQ
基于 AMQP 协议的传统消息队列。
Producer → Exchange → Queue → Consumer
(路由规则)
Exchange 类型:
- Direct: 精确匹配 routing key
- Fanout: 广播到所有绑定队列
- Topic: 通配符匹配 routing key
- Headers: 根据消息头匹配
5.3 Kafka vs RabbitMQ
| 维度 | Kafka | RabbitMQ |
|---|---|---|
| 模型 | 发布-订阅(日志) | 消息队列 |
| 吞吐量 | 百万级/秒 | 万级/秒 |
| 消息保留 | 持久化,可回溯 | 消费后删除 |
| 顺序保证 | 分区内有序 | 队列内有序 |
| 适用场景 | 事件流、日志、大数据 | 任务队列、RPC |
6. 分布式事务
6.1 两阶段提交(2PC)
协调者 参与者A 参与者B
│── Prepare ──────→ │ │
│ │── OK ──→ │
│← OK ─────────── │ │
│ │ │
│── Commit ───────→ │ │
│ │── ACK ─→ │
│← ACK ────────── │ │
问题:
- 阻塞:参与者等待协调者决定
- 单点故障:协调者挂掉,参与者阻塞
- 数据不一致:网络分区时可能产生不一致
6.2 Saga 模式
将大事务拆分为一系列本地事务,每个事务有对应的补偿操作。
T1 → T2 → T3 → ... → Tn
↓ 失败
C3 ← C2 ← C1 ← ... (补偿)
示例:订单流程
1. 创建订单 (T1) 补偿: 取消订单 (C1)
2. 扣减库存 (T2) 补偿: 恢复库存 (C2)
3. 扣款 (T3) 补偿: 退款 (C3)
4. 发送通知 (T4) 补偿: 发送取消通知 (C4)
如果 T3 失败: 执行 C2 → C1
两种编排方式:
| 方式 | 实现 | 优势 | 劣势 |
|---|---|---|---|
| 编排式 (Choreography) | 事件驱动,服务间直接通信 | 解耦 | 难以追踪 |
| 协调式 (Orchestration) | 中央协调器控制流程 | 清晰 | 协调器复杂 |
7. 分布式系统设计原则
| 原则 | 含义 |
|---|---|
| 幂等性 | 同一操作执行多次与执行一次效果相同 |
| 超时与重试 | 设置合理超时,配合指数退避重试 |
| 优雅降级 | 部分功能不可用时,核心功能仍可运行 |
| 可观测性 | 日志、指标、链路追踪(Logging, Metrics, Tracing) |
| 最终一致 | 接受短暂不一致,保证最终收敛 |
与其他主题的关系
- 参见 系统设计,理解服务拆分、缓存、消息队列与容量规划如何连接到分布式场景
- 参见 数据库系统,理解复制、事务、一致性与存储层设计的关系
- 参见 云服务,理解分布式系统如何依赖云平台上的弹性与基础设施能力
- 参见 计算机网络,理解协议、延迟与可靠传输对分布式行为的影响
参考文献
- "Designing Data-Intensive Applications" - Martin Kleppmann
- "Distributed Systems" - Maarten van Steen & Andrew Tanenbaum
- Raft 论文: "In Search of an Understandable Consensus Algorithm"
- Kafka 官方文档