Skip to content

分布式系统:从不可靠组件构建可靠服务

分布式系统由多个通过网络协作的计算节点组成。对使用者来说,它通常表现为一个完整系统;对设计者来说,却必须同时面对节点独立故障、网络延迟、消息乱序、并发写入和时钟偏差。

学习分布式系统的关键,不是背诵中间件名称,而是建立一个基本认识:

网络会延迟或中断,进程会暂停或崩溃,消息可能重复,任何节点看到的都只是局部事实。

为什么分布式系统难

局部故障

单机程序崩溃时,故障通常是明确的;分布式系统则可能只有某个节点、某条链路或某个可用区异常。调用方超时并不等于操作失败:请求可能没有到达,也可能已经成功执行,只是响应丢失。

这也是支付、下单等接口需要设计幂等性的原因。重试是一种恢复手段,但没有幂等约束的重试可能放大故障。

下面的时序图展示了最容易误判的情况:服务端已经提交事务,但响应丢失,客户端看到的只有超时。

图示参考: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 在日志中还原业务上下文?
  • 遥测管道自身发生故障时能否被发现?

最后一组问题就是分布式可观测性要解决的核心。

推荐学习顺序

  1. 先理解失败语义:超时不代表失败,重试不天然安全。
  2. 再理解时间与顺序:学习因果关系、逻辑时钟和一致性模型。
  3. 再学习复制与共识:先掌握 Leader、日志复制和多数派,再阅读 Raft。
  4. 把可靠性落到运行时:学习限流、隔离、降级和 SLO。
  5. 用可观测性验证理解:依次阅读可观测性总览分布式链路追踪分布式日志

学习自测

  1. 客户端调用支付服务超时,为什么不能直接认定支付失败?
  2. CAP 中的“一致性”和数据库 ACID 中的“一致性”是否是同一个概念?
  3. 为什么物理时间戳不足以证明两个跨节点事件的因果关系?
  4. 一个五节点 Raft 集群为什么通常能容忍两个节点不可用?
  5. 如果链路显示数据库调用很慢,还需要查看哪些日志和指标才能继续判断原因?

参考资料