Kyle's Notebook

《DDIA》阅读笔记(一):可靠、可扩展与可维护的应用系统

Word count: 2.5kReading time: 8 min
2020/12/02

《DDIA》阅读笔记(一):可靠、可扩展与可维护的应用系统

本章结构如下:

  • 可靠、可扩展与可维护的应用系统
    • 可靠性(Reliability)
      • 硬件故障
      • 软件故障
      • 人为失误
    • 可扩展性(Scalability)
      • 描述负载
      • 描述性能
      • 系统扩展
        • 自动与手动
        • 水平与垂直
        • 有状态与无状态
    • 可维护性(Maintainability)
      • 可运维性
      • 简单性
        • 复杂系统
        • 设计抽象
      • 可演化性
        • 应对需求变化
        • 敏捷开发模式
        • 简单与抽象
    • * 存储架构
      • 数据库基本架构
        • 客户端通信管理
        • 进程管理
        • 查询处理
        • 事务存储管理
        • 共享组件与工具
      • 传统架构
        • 客户端组件
        • 代理中间件
        • 单元化架构
      • 分布式架构
        • PGXC
        • NewSQL

应用系统需求广泛,单个组件往往无法满足所有数据处理与存储需求。需将任务分解,每个组件负责高效完成其中一部分,多个组件依靠应用层代码驱动有机衔接起来。

数据系统包括数据库、高速缓存、索引、流式处理、批处理等不同访问方式的系统。

区别于计算密集型应用(compute intensive),数据密集型应用(data intensiive)的 CPU 处理能力不是第一限制性因素,关键在于 数据量数据复杂度数据快速多变性

数据系统

人们对软件系统的期望:

  • 执行用户期望的功能。

  • 可容忍应用出错或非正确使用。

  • 可应对典型场景、合理负载压力和数据量。

  • 可防止未经授权的访问和滥用。

可靠性(Reliability)

即使发生故障系统也可以正常工作。

  • 容错、弹性:在预算范围内,系统可应对特定类型的故障(容错测试:Netflix Chaos Monkey)。

  • 故障与失效:分别指组件与系统整体的层面。

要确保系统可靠性通常需要克服以下障碍。

硬件故障

  • 添加冗余减少系统故障率:磁盘配置 RAID;服务器配备双电源,热插拔 CPU;数据中心添加备用电源、发电机等。

  • 结合软件容错,容忍多机失效。

软件故障

认真检查依赖的假设条件与系统之间交互,进行全面的测试,进程隔离,允许进程崩溃并自动重启,反复评估,监控并分析生产环节的行为表现。

人为失误

  • 以最小出错的方式来设计系统。

  • 分离最容易出错的地方、容易引发故障的接口;提供功能齐全、非生产用的沙箱环境。

  • 充分的测试:单元测试,集成测试,系统测试,自动化测试(覆盖边界条件)。

  • 当出现人为失误时,提供快速的恢复机制以尽最减少故障影响。

  • 设置详细而清晰的监控子系统,包括性能指标和错误率。

  • 推行管理流程并加以培训。

可扩展性(Scalability)

可扩展性即当负载参数(数据量、流量或复杂性)增加时,系统应以合理的方式来匹配增长,保持良好性能。

描述负载

负载参数选择取决于系统体系结构(Web 服务:每秒请求处理次数;数据库:写入比例;聊天室:同时活动用户数量;缓存:命中率),可能是平均值或峰值等。

描述性能

负载增加,考虑如果系统资源不变性能会发生什么变化;或保持性能不变需要增加的资源。

  • 批处理系统:吞吐量(单位时间内可处理记录条数、指定数据集上运行作业所需时间)。

  • 在线系统:响应时间,即客户端从发送请求到接收响应之间的间隔(还包括来回网络延迟和各种排队延迟),要求在客户端测量。

  • SLO(服务质量目标)和 SLA(服务质量协议):百分位数,比如 p95 即 95% 分位数为 200ms,要求 95% 的请求快于 200ms,允许 5% 的请求需要更长时间。

  • 后台服务:如一次服务请求包含多次调用,由于长尾效应,关注高百分位数尤为重要(最终用户需要等待最慢的调用完成;即使只有很小百分比的请求缓慢,用户频发触发这种调用,最终总体变慢的概率就会增加)。

系统扩展

超大规模系统往往针对特定应用而高度定制,难以设计出通用架构。

背后取舍因素包括数据读取量、写入量 、待存储的数据量、数据的复杂程度、响应时间要求、访问模式等,或者上述因素的叠加再加上更复杂的问题。

但可扩展架构通常都是从通用模块逐步构建而来,背后往往有规律可循。

自动与手动

某些系统具有弹性特征,它可以自动检测负载增加,然后自动添加更多计算资源,而其他系统则是手动扩展(人工分析性能表现决定添加更多计算)。

如负载高度不可预测,则自动弹性系统会更加高效。手动方式则可以减少执行期间的意外情况。

水平与垂直

在垂直扩展(升级为更强大的机器)和水平扩展(将负载分布到多台机器)之间做取舍。在多台机器上分配负载也被称为 无共享体系结构

在单台机器上运行的系统通常更简单,而高端机器比较昂贵,且扩展水平有限,最终还是无法避免水平扩展。

通常要做出取舍:使用几个强悍的服务器仍可以比大量的小型虚拟机来得更简单、便宜。

有状态与无状态

把无状态服务扩展至多台机器相对容易,而有状态服务从单个节点扩展到分布式多机环境的复杂性会大大增加。通常做法是将数据库运行在一个节点上(垂直扩展),直到高扩展性或高可用性的要求迫使不得不做水平扩展。

可维护性(Maintainability)

随着时间推移,新人员参与到系统开发和运维以维护现有功能或适配新场景等,系统都应高效运转。

系统的可维护性,即可运维性、简单性和可演化性。

可运维性

方便运营团队保持系统平稳运行,数据系统设计:

  • 提供对系统运行时行为和内部的可观测性,方便监控。

  • 支持自动化,与标准工具集成。

  • 避免绑定特定的机器,在整个系统不间断运行的同时允许机器停机维护。

  • 提供良好的文档和易于理解的操作模式, 诸如“如果 X,会发生 Y”。

  • 提供良好的默认配置,且允许管理员在需要时方便地修改默认值。

  • 尝试自我修复,在需要时让管理员手动控制系统状态。

  • 行为可预测,减少意外发生。

简单性

即简化系统复杂性,使新工程师能够轻松理解系统(不是 UI 简单)。

复杂系统

随着项目越来越大,系统会越来越复杂和难以理解。

  • 复杂性拖慢开发效率,增加维护成本,通常表现为:状态空间的膨胀,模块紧耦合,令入纠结的相互依赖关系,不一致的命名和术语,为了性能而采取的特殊处理,为解决某特定问题而引入的特殊框架等。

  • 维护困难:导致预算超支和开发滞后。复杂的软件系统变更引入潜在错误的风险会显著加大,开发人员更加难以准确理解、评估或者更加容易忽略相关系统行为,包括背后的假设、潜在的后果、设计之外的模块交互等。

设计抽象

简化系统设计意味着消除意外方面的复杂性。最好手段之一是抽象:

  • 好的设计抽象可以隐藏大量的实现细节,并对外提供干净易懂的接口。

  • 可用于各种不同的应用程序,复用远比多次重复实现更有效率。

  • 带来更高质量的软件。

质量过硬的抽象组件所带来的好处可使运行其上的所有应用轻松获益。

可演化性

工程师能够轻松地对系统进行改进,并根据需求变化将其适配到非典型场景,也称为可延伸性、易修改性或可塑性。

应对需求变化

适配新的外部环境、新的用例、业务优先级的变化、用户要求的新功能、新平台取代旧平台、法律或监管要求的变化、业务增长促使架构的演变等。

敏捷开发模式

在组织流程方面敏捷开发模式为适应变化提供了很好的参考。其中很多技术工具和模式可帮助在频繁变化的环境中开发软件,例如测试驱动开发和重构(目前多数还只是针对小规模、本地模式环境,例如同一应用程序中的几个源代码文件)。

简单与抽象

由于目标是可以轻松地修改数据系统,使其适应不断变化的需求,这和简单性与抽象性密切相关:简单易懂的系统更容易修改。 其中数据系统级的敏捷性即可演化性。

CATALOG
  1. 1. 《DDIA》阅读笔记(一):可靠、可扩展与可维护的应用系统
    1. 1.1. 可靠性(Reliability)
      1. 1.1.1. 硬件故障
      2. 1.1.2. 软件故障
      3. 1.1.3. 人为失误
    2. 1.2. 可扩展性(Scalability)
      1. 1.2.1. 描述负载
      2. 1.2.2. 描述性能
      3. 1.2.3. 系统扩展
        1. 1.2.3.1. 自动与手动
        2. 1.2.3.2. 水平与垂直
        3. 1.2.3.3. 有状态与无状态
    3. 1.3. 可维护性(Maintainability)
      1. 1.3.1. 可运维性
      2. 1.3.2. 简单性
        1. 1.3.2.1. 复杂系统
        2. 1.3.2.2. 设计抽象
      3. 1.3.3. 可演化性
        1. 1.3.3.1. 应对需求变化
        2. 1.3.3.2. 敏捷开发模式
        3. 1.3.3.3. 简单与抽象