分布式系统:从不可靠组件构建可靠服务
分布式系统由多个通过网络协作的计算节点组成。对使用者来说,它通常表现为一个完整系统;对设计者来说,却必须同时面对节点独立故障、网络延迟、消息乱序、并发写入和时钟偏差。
学习分布式系统的关键,不是背诵中间件名称,而是建立一个基本认识:
网络会延迟或中断,进程会暂停或崩溃,消息可能重复,任何节点看到的都只是局部事实。
为什么分布式系统难
局部故障
单机程序崩溃时,故障通常是明确的;分布式系统则可能只有某个节点、某条链路或某个可用区异常。调用方超时并不等于操作失败:请求可能没有到达,也可能已经成功执行,只是响应丢失。
这也是支付、下单等接口需要设计幂等性的原因。重试是一种恢复手段,但没有幂等约束的重试可能放大故障。
下面的时序图展示了最容易误判的情况:服务端已经提交事务,但响应丢失,客户端看到的只有超时。
图示参考:Amazon Builders' Library:Timeouts, retries and backoff with jitter
没有全局时钟
不同机器的物理时钟存在偏差,不能仅凭两个本地时间戳断言事件的因果顺序。Lamport 在经典论文中用 happened-before 关系和逻辑时钟描述事件的偏序关系,为复制、快照和一致性协议奠定了基础。
并发与状态复制
多个节点可能同时接受写入,而复制存在延迟。系统必须回答:
- 哪个副本可以接受写入?
- 何时可以向客户端确认成功?
- 读请求允许看到多旧的数据?
- 冲突由系统拒绝、排序,还是交给业务合并?
这些问题共同决定系统的一致性模型,而不是由“用了分布式数据库”自动解决。
网络分区下必须做取舍
CAP 定理讨论的是:在发生网络分区时,一个系统无法同时保证线性一致性和每个请求都得到非错误响应。工程上的问题不是笼统地“从三个字母中选两个”,而是明确:
- 哪些操作在分区期间拒绝服务,以保护一致性?
- 哪些操作可以接受旧值或冲突,以维持可用性?
- 分区恢复后如何检测并解决差异?
故障可能被放大
超时、重试和自动扩缩容如果缺少边界,可能形成重试风暴、排队膨胀或级联故障。可靠性设计不仅要考虑组件能否恢复,还要考虑恢复动作是否会给下游增加压力。
核心知识地图
| 主题 | 要解决的问题 | 常见机制 |
|---|---|---|
| 通信 | 节点如何交换信息 | RPC、消息队列、超时、重试、背压 |
| 数据分片 | 数据如何分散到多个节点 | 哈希分片、范围分片、一致性哈希 |
| 数据复制 | 节点故障后如何继续提供数据 | 主从、多主、无主复制、法定人数 |
| 一致性 | 客户端能看到什么结果 | 线性一致性、因果一致性、最终一致性 |
| 协调与共识 | 多个节点如何对顺序或值达成一致 | Leader 选举、Raft、Paxos、租约 |
| 容错 | 如何限制和恢复故障 | 幂等、熔断、限流、隔离、降级 |
| 可观测性 | 如何从外部理解系统内部状态 | 指标、日志、链路、事件、性能剖析 |
这些主题彼此关联。例如,一个带重试的 RPC 同时涉及通信、幂等、一致性和可观测性;一个复制数据库同时涉及分片、故障检测、共识和恢复。
一个实用的分析框架
遇到新的分布式系统或故障场景时,可以依次问下面五组问题。
1. 状态在哪里
- 哪些组件有状态,哪些组件可以随时重建?
- 数据有几个副本,副本分布在哪里?
- 谁是事实来源(source of truth)?
2. 成功如何定义
- 请求在哪个时间点对客户端返回成功?
- 成功是否意味着已经持久化、完成复制或仅进入队列?
- 客户端超时后重试是否安全?
3. 顺序如何建立
- 业务是否真的需要全局顺序,还是每个用户、订单或分区内有序即可?
- 顺序来自单个 Leader、逻辑时钟还是消息代理的分区?
- 并发冲突如何被发现和处理?
4. 故障如何隔离
- 如果下游变慢,上游会超时、排队还是继续重试?
- 是否设置了请求截止时间、重试预算和最大队列长度?
- 单个租户、分区或可用区故障会影响多大范围?
5. 如何证明判断
- 哪个指标先显示异常?
- 能否通过链路找到具体慢调用?
- 能否使用
trace_id在日志中还原业务上下文? - 遥测管道自身发生故障时能否被发现?
最后一组问题就是分布式可观测性要解决的核心。
推荐学习顺序
- 先理解失败语义:超时不代表失败,重试不天然安全。
- 再理解时间与顺序:学习因果关系、逻辑时钟和一致性模型。
- 再学习复制与共识:先掌握 Leader、日志复制和多数派,再阅读 Raft。
- 把可靠性落到运行时:学习限流、隔离、降级和 SLO。
- 用可观测性验证理解:依次阅读可观测性总览、分布式链路追踪和分布式日志。
学习自测
- 客户端调用支付服务超时,为什么不能直接认定支付失败?
- CAP 中的“一致性”和数据库 ACID 中的“一致性”是否是同一个概念?
- 为什么物理时间戳不足以证明两个跨节点事件的因果关系?
- 一个五节点 Raft 集群为什么通常能容忍两个节点不可用?
- 如果链路显示数据库调用很慢,还需要查看哪些日志和指标才能继续判断原因?
参考资料
- Leslie Lamport:Time, Clocks and the Ordering of Events in a Distributed System
- Seth Gilbert、Nancy Lynch:Brewer's Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services
- Michael J. Fischer、Nancy A. Lynch、Michael S. Paterson:Impossibility of Distributed Consensus with One Faulty Process
- Diego Ongaro、John Ousterhout:In Search of an Understandable Consensus Algorithm
- Jeffrey Dean、Sanjay Ghemawat:MapReduce: Simplified Data Processing on Large Clusters
- Google SRE Book:Monitoring Distributed Systems
- Amazon Builders' Library:Timeouts, retries and backoff with jitter