架构设计

DDIA 第一章:可靠、可扩展与可维护的应用|学习笔记|2026-09-10

可靠性看故障,可扩展性看增长,可维护性看长期变化。

**一句话总结:**可靠性看故障,可扩展性看增长,可维护性看长期变化。

📚 学习来源:《Designing Data-Intensive Applications》第一章 Reliable, Scalable, and Maintainable Applications 学习日期:2026-09-10 案例主线:电商商品系统

一、数据密集型应用

现代数据系统通常由数据库、缓存、搜索索引和消息队列等通用组件组合而成。业务服务通过统一 API 隐藏内部实现,并向用户承诺数据正确性、性能与可用性。此时,应用开发者也成为了数据系统设计者。

典型商品数据链路:用户请求 → 商品服务 → 商品数据库、Redis 缓存、搜索索引和变更消息队列。

第一章使用三个维度评价数据系统:

  • 可靠性 Reliability:发生故障时,系统仍能正确工作。
  • 可扩展性 Scalability:数据量、流量或复杂度增长时,有合理的应对方式。
  • 可维护性 Maintainability:系统长期演进、多人维护时,仍能高效运维、理解和修改。

二、可靠性

Fault 与 Failure

  • Fault(故障):某个组件偏离预期行为。
  • Failure(失效):系统整体无法向用户提供承诺的服务。
  • Fault tolerance(容错):阻止局部 fault 演变成整体 failure。

Fault 是内部发生了问题;failure 是问题突破系统边界,影响了服务承诺。

案例:Redis 不可用后,商品服务自动回源数据库。响应从 30ms 增加到 120ms,但数据正确且仍低于 200ms SLA。这是 fault,但不是 failure。如果 SLA 是 100ms,则同一事件也构成 failure。

三类故障

  1. 硬件故障:磁盘、网卡、机器或机房故障。常用冗余、复制和故障转移处理。
  2. 软件错误:连接池泄漏、异常输入导致所有实例崩溃、缓存故障引发数据库雪崩。软件错误可能跨节点相关,更容易造成级联故障。
  3. 人为错误:错误配置、误删数据、发布错误。应通过权限隔离、灰度发布、审计、自动化和回滚机制降低风险。

级联故障案例

Redis 故障 → 大量请求回源 → 数据库过载 → 连接池耗尽 → 商品服务整体失效。

保护措施包括:

  • Redis 主备或备用集群切换;
  • 本地缓存、旧值或静态数据兜底;
  • 热点 Key 请求合并;
  • 数据库限流与熔断;
  • 核心信息优先,非核心功能降级;
  • 缓存预热和故障演练。

仅准备备用路径还不够,必须验证备用路径在真实故障下仍然可用。

三、可扩展性

负载参数

“支持高并发”不是可验证的描述。必须使用具体参数描述负载:

  • 读写 QPS;
  • 读写比例;
  • 热点集中度;
  • 数据总量与增长速度;
  • 商品价格更新速率;
  • MQ 消息生产与消费速率;
  • 同时在线用户数。

需要区分:

  • 负载参数:例如输入 QPS、写入速率、索引数据量。
  • 容量配置:例如缓存大小、机器数量。
  • 运行结果或压力信号:例如 MQ 积压量、P99、错误率。

MQ 积压量应结合生产速率、消费速率和最老消息等待时间分析。消费速度大于生产速度时积压正在恢复,反之则继续恶化。

性能指标

  • 吞吐量:单位时间内成功完成的工作量,可以用请求/秒、事务/秒、消息/秒或 MB/秒表示。
  • QPS:每秒查询或请求数,是吞吐量的一种具体计量方式。当工作单位就是请求时,成功 QPS 就是吞吐量。
  • 输入 QPS:每秒到达的请求数,不一定等于系统成功完成的吞吐量。
  • 响应时间:从请求发出到收到结果的总时间,包含执行、排队和网络等待等时间。

分位数:

  • P50:50% 请求不超过该时间,描述典型请求。
  • P95:95% 请求不超过该时间。
  • P99:99% 请求不超过该时间,用于观察长尾。
  • P99.9:用于要求更高的尾延迟场景。

平均响应时间会掩盖偏斜分布和少量极慢请求。商品页并行依赖多个下游服务时,只要其中一个服务变慢,页面就会变慢,形成尾延迟放大。

扩容方式

  • 纵向扩展:提高单机 CPU、内存、磁盘和网络能力。架构变化小,但存在硬件上限和单机风险。
  • 横向扩展:增加服务实例、缓存分片、搜索节点、数据库分片或 MQ 消费者。无状态服务较容易,有状态组件需要处理分片、复制、一致性和恢复。
  • 手动扩容:稳定可预测,但响应较慢。
  • 弹性扩容:能够随负载变化,但存在启动延迟和扩缩容震荡风险。

大促计算案例

当前流量为 5,000 QPS,缓存命中率为 95%,数据库承担:

$$

5,000 \times (1-95%) = 250\ \mathrm{QPS}

$$

大促流量增长到 20,000 QPS,命中率不变时,数据库承担:

$$

20,000 \times (1-95%) = 1,000\ \mathrm{QPS}

$$

若数据库最大稳定容量为 600 QPS,仅增加商品服务实例无法解决瓶颈。缓存命中率至少需要达到:

$$

1-\frac{600}{20,000}=97%

$$

实际系统还应预留容量安全余量。可结合只读副本、热点本地缓存、请求合并、缓存预热、数据库限流和分片处理。

增加只读副本需要明确商品价格允许多大的主从延迟;限流可以保护数据库,但需要定义被拒绝请求的降级策略。

四、可维护性

DDIA 将可维护性拆成三个方面:

可运维性 Operability

让开发和运维人员容易观察、管理和恢复系统。典型能力包括统一日志、指标、链路追踪、自动化部署、容量规划、变更记录和运行手册。

只能逐台登录服务器查日志,属于可运维性差。

简单性 Simplicity

控制不必要的偶然复杂度,使系统更容易理解。多个服务共享数据库、循环依赖,以及同一业务概念存在多套定义,都会增加复杂度。

商品表被十几个服务直接访问,主要是简单性问题,也会损害可演化性。

可演化性 Evolvability

系统能够安全适应新需求。新增价格类型或商品类型时,如果必须修改大量服务并协同发布,说明系统可演化性差。

五、综合案例

商家修改商品价格后,数据库和缓存已经更新,但 MQ 严重积压,Elasticsearch 两小时未更新:

  • 可靠性:搜索页与详情页价格不一致。是否构成 failure,取决于系统承诺的索引同步时效。
  • 可扩展性:负载增长后,MQ 生产速度超过消费速度,且 P99 明显升高,说明架构无法合理承载增长后的负载。
  • 可维护性:缺少统一链路追踪,只能跨服务手动查询日志,属于可运维性不足。

六、架构评审模板

1. 定义正确工作

  • 系统对用户承诺什么?
  • 哪些数据必须准确?
  • 允许多长时间的数据延迟?
  • 可用性和延迟 SLA 是什么?

2. 描述负载

  • 读写 QPS 和比例是多少?
  • 流量是否存在热点?
  • 数据量如何增长?
  • MQ 的生产与消费速率是多少?
  • 哪种负载参数最能代表业务压力?

3. 描述性能

  • 系统吞吐量是多少?
  • P50、P95 和 P99 分别是多少?
  • 负载增长时,哪些性能指标首先恶化?
  • 哪个组件最先达到容量上限?

4. 分析可靠性

  • 需要容忍哪些硬件、软件和人为故障?
  • 哪些 fault 可能演变为用户可见的 failure?
  • 如何检测、隔离、降级和恢复?
  • 容错路径是否经过真实演练?

5. 分析可维护性

  • 系统是否容易观察、定位和恢复?
  • 是否存在共享数据库、循环依赖等偶然复杂度?
  • 新需求能否局部修改、独立发布和安全回滚?

七、面试表达

我主要从可靠性、可扩展性和可维护性三个维度评价数据系统。可靠性关注局部故障是否会演变为用户可见的服务失效;可扩展性需要先用 QPS、读写比例、热点程度和数据量描述负载,再通过吞吐量和 P99 等指标观察负载增长后的表现;可维护性则包括系统是否容易运维、是否控制了不必要的复杂度,以及是否能够适应未来需求变化。

八、本次互动练习结论

  • Redis 故障但数据库回源仍满足 SLA:有 fault、无 failure。
  • Redis 故障引发数据库过载并导致接口超时:fault 演变成 failure。
  • 缓存大小是容量配置,索引大小是数据负载维度,MQ 积压是运行结果和压力信号。
  • 20,000 QPS、95% 缓存命中率时,数据库承担 1,000 QPS。
  • 分散日志影响可运维性,共享数据库增加复杂性,大量条件分支损害可演化性。
  • 吞吐量是通用工作量指标;QPS 是以请求数为单位的速率指标。