1. 为什么需要分布式系统
1.1 三类驱动力
- 可扩展性(Scalability):单机 CPU、内存、磁盘都有物理上限,纵向扩展(Scale Up)成本呈指数上升,横向扩展(Scale Out)更经济。
- 高可用(Availability):单机总有故障概率,多副本冗余才能把整体可用性从 99% 提到 99.99%。
- 地理分布(Latency):用户遍布全球时,把数据放到离用户近的地方才能降低延迟。
1.2 分布式系统的代价
分布式不是免费的午餐,它引入了单机系统根本不存在的复杂性:
- 部分失效:一部分节点挂了,其他节点还在跑,系统处于「既不成功也不失败」的中间态。
- 网络不可靠:消息可能丢失、重复、乱序、延迟任意长。
- 无全局时钟:各节点时钟有偏差,无法简单比较事件先后。
- 并发与一致性:多副本并发写入会产生冲突,需额外机制收敛。
一句话概括:分布式系统的核心难点是「如何在不可靠的组件之上,构建对上层表现为可靠的整体」。
2. 基本模型与故障模型
2.1 两类故障
| 类型 | 表现 | 处理难度 |
|---|---|---|
| 崩溃故障 Crash | 节点停止响应,不再发消息 | 相对可控 |
| 拜占庭故障 Byzantine | 节点可能发送错误甚至恶意消息 | 极难,需 BFT 共识 |
绝大多数工程系统假设崩溃-恢复(Crash-Recovery)模型:节点可能崩溃,但重启后不撒谎(可能丢内存状态,磁盘数据保留)。区块链场景才需要拜占庭容错。
2.2 同步 vs 异步
- 同步模型:消息延迟有上界,可用超时判定节点死亡。
- 异步模型:延迟无上界,无法区分「节点慢」与「节点死」——这是 FLP 不可能定理的根源。
- 部分同步模型:现实中常用的折中,大部分时间同步,偶尔异步。
FLP 不可能定理:在完全异步、允许一个节点崩溃的系统中,不存在既保证安全性又保证活性的确定性共识算法。工程上靠随机化或超时绕开。
3. CAP 定理
3.1 三个字母的含义
- C(Consistency)一致性:所有节点读到同一份最新数据,等价于线性一致性。
- A(Availability)可用性:每个请求都能在有限时间内得到非错误响应。
- P(Partition tolerance)分区容忍:网络分区时系统继续工作。
3.2 正确理解
CAP 不是说「三选二」,而是:当分区发生时,只能在 C 与 A 之间二选一。 网络分区是客观事实,P 无法放弃,所以真正的抉择是 CP 还是 AP。
网络正常时: C 与 A 可同时满足
网络分区时: ┌── 选 C(拒绝服务,保一致)→ CP,如 ZooKeeper、etcd
└── 选 A(继续服务,容忍不一致)→ AP,如 Cassandra、Dynamo
3.3 PACELC 补充
CAP 只描述了分区期间的行为,PACELC 补上了正常时的权衡:
if Partition: 在 A 与 C 之间选
Else: 在 Latency 与 C 之间选
即:即使没有分区,多副本同步复制也会带来延迟,系统仍要在「低延迟」与「强一致」之间取舍。
4. 一致性模型
4.1 一致性谱系
| 模型 | 含义 | 典型系统 |
|---|---|---|
| 线性一致 Linearizable | 表现为单一副本,读写实时有序 | etcd、Spanner |
| 顺序一致 Sequential | 所有节点看到同一顺序,但不要求实时 | 部分内存模型 |
| 因果一致 Causal | 有因果关系的操作保持顺序,并发可乱序 | COPS、部分 KV |
| 读己之写 Read-Your-Writes | 用户能读到自己的更新 | 会话一致性方案 |
| 最终一致 Eventual | 停止写入后最终收敛 | DNS、Cassandra |
4.2 线性一致性的价值
线性一致让分布式系统看起来像一台单机,是最容易推理的模型,但代价是延迟高(需跨节点协调)。ZooKeeper、etcd 这类协调服务必须提供线性一致(至少写线性化)。
4.3 会话保证
工程上常用一组折中保证替代完整强一致:
- 单调读:读到的版本不会倒退。
- 读己之写:读自己刚写的值。
- 单调写:同一客户端的写按序应用。
这些保证足以满足大多数业务,且无需全局协调。
5. 复制策略
5.1 主从复制 Leader-Follower
写请求 → Leader ──复制日志──▶ Follower1
└──────▶ Follower2
读请求 → 可路由到任意副本(取决于一致性要求)
- 优点:写入顺序由 Leader 单点确定,无写冲突;实现简单。
- 缺点:Leader 是瓶颈与单点(需选举恢复);故障切换期间可能不可用。
5.2 同步 vs 异步复制
| 方式 | 一致性 | 延迟 | 数据丢失风险 |
|---|---|---|---|
| 同步 | 强 | 高(等最慢副本) | 无 |
| 半同步 | 中 | 中(等一个副本) | 低 |
| 异步 | 最终 | 低 | Leader 挂会丢未复制数据 |
MySQL 半同步、Kafka acks=all 都是这一谱系的具体实现。
5.3 去中心复制模型
客户端可写任意节点,节点间通过 Gossip 传播,冲突用 向量时钟检测、用 CRDT / LWW 合并。DynamoDB、Cassandra 属此类,可用性高但只提供最终一致。
6. 共识算法
6.1 共识要解决什么
多个节点就同一个值(或同一串操作日志顺序)达成一致,且满足:
- 一致性(Agreement):所有正确节点决定相同值。
- 有效性(Validity):决定的值必须是某个节点提议过的。
- 终止性(Termination):最终都能做出决定。
6.2 Raft 核心流程
Raft 把共识拆成领导者选举 + 日志复制 + 安全性三块,比 Paxos 更易理解:
角色: Leader / Follower / Candidate
任期: 逻辑时钟,单调递增,用于识别过期消息
选举:
① Follower 超时未收到心跳 → 变 Candidate,任期 +1
② 向所有节点拉票,先到先得
③ 获得多数派(N/2 + 1)票 → 成为 Leader
④ 开始周期性发送心跳维持权威
日志复制:
① 客户端请求 → Leader 追加到本地日志(未提交)
② 并行发送 AppendEntries 给所有 Follower
③ 多数派确认后 → 提交该条目,应用到状态机
④ 通知 Follower 提交
多数派(Quorum)是关键:任意两个多数派必有交集,保证新 Leader 一定包含已提交的日志,从而不会丢失已确认的写。
6.3 选举超时的随机化
若所有 Follower 同时超时,会平分选票导致活锁。Raft 给每个节点随机化选举超时(如 150~300ms),使通常只有一个节点先发起选举,快速收敛。
7. 时间、时钟与顺序
7.1 物理时钟不可靠
NTP 同步后仍有毫秒级偏差,且可能回拨。不能用物理时间戳判定事件的因果顺序。
7.2 逻辑时钟
- Lamport 时钟:每个事件递增计数器,消息携带时间戳,接收方取
max(本地, 收到) + 1。给出偏序:a → b则L(a) < L(b),反之不成立。 - 向量时钟:每节点维护一个向量,可判断并发与因果:若
V(a) < V(b)则 a 先于 b;若互不包含则并发。代价是空间随节点数增长。 - 混合逻辑时钟 HLC:物理时间 + 逻辑计数,兼顾近似实时与因果(CockroachDB 使用)。
7.3 全序广播
若所有节点对消息顺序达成一致(全序广播 Total Order Broadcast),则等价于共识。Raft 的日志复制本质上就是全序广播。
8. 典型架构与工程实践
8.1 常见模式
- 协调服务:etcd/ZooKeeper 提供选主、配置、分布式锁,基于共识算法。
- 分片 + 复制:数据按 key 分片(Sharding),每片多副本,兼顾扩展与容错。
- 读写分离:写走 Leader,读走 Follower,需处理复制延迟导致的读到旧值。
- 幂等设计:请求带唯一 ID,服务端去重,使重试安全。
8.2 工程清单
超时与重试: 指数退避 + 抖动,避免重试风暴
熔断降级: 下游故障时快速失败,保护自身
幂等键: 写操作携带 request_id 去重
一致性哈希: 节点增减时最小化数据迁移
健康检查: 区分存活(liveness)与就绪(readiness)
8.3 超时值的经验
超时不是拍脑袋定的:应基于P99 延迟设定,并小于上层超时,形成逐层收敛的超时预算(Timeout Budget),避免「重试放大」把下游打垮。
9. 常见陷阱
- 把 CAP 理解为「三选二」:实际是分区时的 C/A 取舍,正常时还要考虑延迟。
- 用物理时钟排序事件:时钟回拨会导致顺序错乱,需逻辑时钟或 HLC。
- 认为重试总是安全:非幂等操作重试会造成重复扣款、重复下单。
- 忽视脑裂:网络分区时两个 Leader 同时写,需多数派机制或 fencing token 防止。
- 过度追求强一致:全局线性一致延迟高,多数业务用会话一致性即可。
- 忘记处理复制延迟:读写分离后立即读可能读到旧值,需读己之写保证。
- 无界重试:没有退避与上限的重试会放大故障,形成雪崩。
参考文章
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。