**一句话总结:**可靠性看故障,可扩展性看增长,可维护性看长期变化。
📚 学习来源:《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。
三类故障
- 硬件故障:磁盘、网卡、机器或机房故障。常用冗余、复制和故障转移处理。
- 软件错误:连接池泄漏、异常输入导致所有实例崩溃、缓存故障引发数据库雪崩。软件错误可能跨节点相关,更容易造成级联故障。
- 人为错误:错误配置、误删数据、发布错误。应通过权限隔离、灰度发布、审计、自动化和回滚机制降低风险。
级联故障案例
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 是以请求数为单位的速率指标。