0%

DDIA Chapter 01 可靠性,可拓展性,可维护性

本章关键字:可靠、可拓展、可维护(的数据系统)。澄清它们的定义,概述考量这些目标的方法。
当今, 数据密集型(data-intensive) 应用多于 计算密集型(compute-intensive) 应用。CPU很少成为瓶颈,问题通常来自数据量、数据复杂性、以及数据的变更速度。

一些耳熟能详的组件:

  • 存储数据,以便自己或其他应用程序之后能再次找到 (数据库(database)
  • 记住开销昂贵操作的结果,加快读取速度(缓存(cache)
  • 允许用户按关键字搜索数据,或以各种方式对数据进行过滤(搜索索引(search indexes)
  • 向其他进程发送消息,进行异步处理(流处理(stream processing)
  • 定期处理累积的大批量数据(批处理(batch processing)

大型应用程序有着各种严格而广泛的要求。这些数据存储工具与数据处理工具,它们针对不同应用场景进行优化,单个工具不足以满足所有的数据处理和存储需求。总体工作被拆分成一系列能被单个工具高效完成的任务,并通过应用代码将它们缝合起来。

在尝试将多个工具组合在一起提供服务时,设计者会重点讨论三个在大多数软件系统中都很重要的问题:

可靠性(Reliability)

​ 系统在困境(adversity)(硬件故障、软件故障、人为错误)中仍可正常工作(正确完成功能,并能达到期望的性能水准)。

可拓展性(Scalability)

​ 有合理的办法应对系统的增长(数据量、流量、复杂性)

可维护性(Maintainability)

​ 许多不同的人(工程师、运维)在不同的生命周期,都能高效地在系统上工作(使系统保持现有行为,并适应新的应用场景)。

可靠性

可以把可靠性粗略理解为“即使出现问题,也能继续正确工作”。
造成错误的原因叫做故障(fault),能预料并应对故障的系统特性可称为容错(fault-tolerant)
故障(fault)不同于失效(failure)故障通常定义为系统的一部分状态偏离其标准,而失效则是系统作为一个整体停止向用户提供服务。好的容错机制设计防止因故障而导致失效

硬件故障

由众多机器组成的分布式系统中,硬件异常不再”异常”。硬件故障是随机的、相互独立的。通过增加单个硬件的冗余度来解决硬件异常可能带来的问题。
在硬件冗余的基础上引入软件容错,那么系统在容忍整个(单台)机器故障的道路上就更进一步了。运维上也有便利,例如:如果需要重启机器(例如应用操作系统安全补丁),单服务器系统就需要计划停机。而允许机器失效的系统则可以一次修复一个节点,无需整个系统停机。

软件错误

另一类错误是内部的系统性错误(systematic error)。这类错误难以预料,而且因为是跨节点相关的,所以比起不相关的硬件故障往往可能造成更多的系统失效
软件BUG通常会潜伏很长时间,况意味着软件对其环境做出了某种假设——虽然这种假设通常来说是正确的,但由于某种原因最后不再成立了。
对策:

  • 仔细考虑系统中的假设和交互;
  • 彻底的测试。
  • 进程隔离,允许进程崩溃并重启。
  • 测量、监控并分析生产环境中的系统行为。如果系统能够提供一些保证(例如在一个消息队列中,进入与发出的消息数量相等),那么系统就可以在运行时不断自检,并在出现差异(discrepancy) 时报警。

人为错误

人类不可靠。
对策:

  • 以最小化犯错机会的方式设计系统。
  • 将人们最容易犯错的地方与可能导致失效的地方解耦(decouple)沙箱(sandbox)
  • 在各个层次进行彻底的测试。
  • 允许从人为错误中简单快速地恢复,以最大限度地减少失效情况带来的影响。 如快速回滚,分批发布。
  • 配置详细和明确的监控,及时发现和日后分析问题。
  • 良好的管理实践与充分的培训——一个复杂而重要的方面,但超出了本书的范围。

可拓展性

可拓展性(Scalability) 是用来描述系统应对负载增长能力的术语。讨论可拓展性意味着考虑诸如“如果系统以特定方式增长,有什么选项可以应对增长?”和“如何增加计算资源来处理额外的负载?”等问题。

描述负载

描述负载的参数的最佳选择取决于系统架构,它可能是每秒向Web服务器发出的请求、数据库中的读写比率、聊天室中同时活跃的用户数量、缓存命中率或其他东西。

描述性能

系统负载参数定义好后,就可以研究当负载增加会发生什么。两种角度:

  • 增加负载参数并保持系统资源(CPU、内存、网络带宽等)不变时,系统性能将受到什么影响?
  • 增加负载参数并希望保持性能不变时,需要增加多少系统资源?

如何描述性能(例):

  • 批处理系统:重要的是吞吐量(throughput)
  • 在线系统:重要的是服务的响应时间(response time)

    延迟和响应时间

    延迟(latency)响应时间(response time) 经常用作同义词,但实际上它们并不一样。响应时间是客户所看到的,除了实际处理请求的时间( 服务时间(service time) )之外,还包括网络延迟和排队延迟。延迟是某个请求等待处理的持续时长,在此期间它处于 休眠(latent) 状态,并等待服务。

单次服务的响应时间总会有差异,所以需要观测多次服务结果的分布(distribution)
比起算数平均值,中位数有更好的说服力(或与中位数类似的,90%以上的请求可以在x毫秒之内响应)。
实践中,一个请求往往需要多个后端调用,如果其中一个响应较慢便会拖累整个请求的响应时间。

应对负载

垂直拓展(vertical scaling):转向更强大的机器
水平拓展(horizontal scaling):将负载分布到多台小机器上
如果负载极难预测(highly unpredictable),则检测到负载增加时自动增加计算资源的弹性(elastic)系统可能很有用
围绕假设(assumption) 建立良好的可拓展架构

可维护性

系统更新,代码会不再符合新的需求;人员更替,知识会流失。

可操作性(Operability)

​ 便于运维团队保持系统平稳运行。

简单性(Simplicity)

​ 消除尽可能多的复杂度(complexity),使新工程师易学。(和用户接口的简单性不一样。)

可演化性(evolability)

​ 使工程师在未来能轻松地对系统进行更改,当需求变化时为新应用场景做适配。也称为可拓展性(extensibility)可修改性(modifiability)可塑性(plasticity)
(extensibility是业务逻辑,服务范围方面的拓展性;Scalability是增加算力增加储存空间等性能方面的拓展性)

Developer Happiness

运维职责包括但不限:

  • 监控系统的运行状况,并在服务状态不佳时快速恢复服务
  • 跟踪问题的原因,例如系统故障或性能下降
  • 及时更新软件和平台,比如安全补丁
  • 了解系统间的相互作用,以便在异常变更造成损失前进行规避。
  • 预测未来的问题,并在问题出现之前加以解决(例如,容量规划)
  • 建立部署,配置、管理方面的良好实践,编写相应工具
  • 执行复杂的维护任务,例如将应用程序从一个平台迁移到另一个平台
  • 当配置变更时,维持系统的安全性
  • 定义工作流程,使运维操作可预测,并保持生产环境稳定。
  • 铁打的营盘流水的兵,维持组织对系统的了解。

实现幸福的方式:

  • 通过良好的监控,提供对系统内部状态和运行时行为的可见性(visibility)
  • 为自动化提供良好支持,将系统与标准化工具相集成
  • 避免依赖单台机器(在整个系统继续不间断运行的情况下允许机器停机维护)
  • 提供良好的文档和易于理解的操作模型(“如果做X,会发生Y”)
  • 提供良好的默认行为,但需要时也允许管理员自由覆盖默认值
  • 有条件时进行自我修复,但需要时也允许管理员手动控制系统状态
  • 行为可预测,最大限度减少意外

用抽象消除额外复杂度,当状态空间激增、模块间紧密耦合、纠结的依赖关系、不一致的命名和术语、解决性能问题的Hack、需要绕开的特例等使复杂度激增的情况发生时。
可演化性(evolvability),系统不会一成不变,为添加各种奇怪的需求做好准备。