什么是分布式系统的可靠性,可扩展性和可维护性

119 阅读7分钟

可靠性(Reliability)

什么是可靠性

可靠性:即使出现问题也能继续正常工作。

具体来说:

  • 应用程序表现出用户所期望的功能。
  • 允许用户犯错,允许用户以出乎意料的方式使用软件。
  • 在预期的负载和数据量下,性能满足要求。
  • 系统能防止未经授权的访问和滥用。

相关的名词:

  • 故障(fault):系统的一部分状态偏离其标准
  • 失效(failure):系统作为一个整体停止向用户提供服务
  • 容错(fault-tolerant):能预料并应对故障的系统特性

故障的类型以及如何预防

首先,故障不等于失效

硬件故障:一旦你拥有很多机器,这些事情总会发生

  • 增加单个硬件的冗余度。例:磁盘组件RAID、服务器双路电源和热插拔CPU、备用电源等

软件错误:内部的系统性错误,难以预料

  • 仔细考虑系统中的假设和交互
  • 彻底的测试
  • 进程隔离
  • 允许进程崩溃并重启;测量
  • 监控并分析生产环境中的系统行为

人为错误:运维配置错误是导致服务中断的首要原因

  • 良好的系统设计,例如精心设计的抽象、API和管理后台
  • 将容易犯错的地方与可能导致失效的地方解耦(decouple)
  • 允许快速恢复,例如回滚配置变更、分批发布新代码、提供数据重算工具
  • 配置详细和明确的监控,比如性能指标和错误率(即遥测)
  • 良好的管理实践与充分的培训

可扩展性

什么是可扩展性

可扩展性(Scalability)

  • 用来描述系统应对负载增长能力的术语。
  • 讨论“如果系统以特定方式增长,有什么选项可以应对增长?”和“如何增加计算资源来处理额外的负载?”的问题

描述负载

  • 简要描述系统当前的负载。用 负载参数(load parameters)描述。
  • 参数的最佳选择取决于系统架构。例如每秒请求数、数据库读写比、活跃用户数量等。

描述性能

系统的负载被描述好后,就可以讨论负载增加时会发生什么,观察的角度包括:

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

对于响应时间来说,最好使用百分位点(percentiles)而非平均值。可以用中位数和尾部延迟来判断响应速度

  • 尾部延迟:
    • tail latencies,即高百分点位的响应时间,例如最慢的95%、99.9%的用户的响应时间。
    • 实践中,尾部延迟直接影响着用户体验,因为请求响应更慢的客户数据更多所以往往更有价值。
    • 排队延迟(queueing delay)占了尾部延迟中的很大一部分,总体响应时间包括等待先前请求完成的时间。

应对负载

当负载增加时,如何保持良好的性能?答案是扩展。

扩展:

  • 纵向扩展(scaling up),也称垂直扩展(vertical scaling)
    • 使用性能更强大的机器
    • 运行在单台机器上的系统更加简单
  • 横向扩展(scaling out),也称水平扩展(horizontal scaling)
    • 增加机器的数量,将负载分摊到更多的机器上
    • 由于资金问题,密集的负载无法避免地需要横向扩展
    • 跨多台机器分配负载也称为“无共享(shared-nothing) ”架构

优秀架构需要将这两种方法务实地结合,因为使用几台强大的机器可能比使用大量的小型虚拟机更简单也更便宜。

普通的系统需要手动扩展系统资源,但当负载极难预测时,可以使用弹性(elastic)系统,当检测到负载增加时会自动增加计算资源。

可扩展架构基于假设,但是如果假设错了,那么前期为扩展所做的投入就浪费了。因此,早期创业公司更需要产品“快速迭代”能力。

可维护性

什么是可维护性

运维阶段占了软件的大部分开销,包括修复漏洞、保持系统正常运行、调查失效、适配新的平台、为新的场景进行修改、偿还技术债、添加新的功能等。

可维护性强的系统能减少维护的痛苦,在运维阶段更少的修复遗留问题和避免变为遗留系统。

三个设计原则

  • 可操控性:便于运维团队保持系统平稳运行
  • 简单性:消除尽可能多的复杂度,使新的工程师也能轻松理解系统
  • 可演化性:使系统能更轻松的进行更改,当需求变化时为新应用场景做适配,也称为可扩展性、可修改性、可塑性。

可操作性

  • 运维团队对于保持软件系统顺利运行至关重要,一个优秀的运营团队有以下职责:

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

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

简单性:管理复杂度

随着项目越来越大,代码往往变得非常复杂且难以理解,慢慢的变成屎山。复杂度(complexity)有各种可能的症状,例如:状态空间激增、模块间紧密耦合、纠结的依赖关系、不一致的命名和术语、解决性能问题的Hack、需要绕开的特例等等。

因为复杂度导致维护困难时,预算和时间安排通常会超支,在复杂的软件中进行变更,引入错误的风险也更大。当开发人员难以理解系统时,隐藏的假设、无意的后果和意外的交互就更容易被忽略。

降低复杂度能极大提高软件可维护性。同时,简化系统不一定是减少功能,也可以是消除额外复杂度。额外复杂度是由具体实现中涌现而非问题本身固有的复杂度。

消除额外复杂度最好的工具是抽象。一个好的抽象隐藏实现细节且外观简单易懂,并且能广泛用于各类的不同应用。

可演化性:拥抱变化

系统的需求通常处于常态的变化中,而简单易懂的系统通常比复杂的系统更容易修改,这与简单性和抽象性密切相关。

在组织流程方面,敏捷工作模式为适应变化提供了一个框架,敏捷社区开发了对频繁变化的环境中开发软件很有帮助的技术工具和模式,例如测试驱动开发(TDD)和重构(refactoring)。这些技术可以用于小规模的代码(同一个应用中几个代码文件)中。