每人都有直观认识,即什么代表可靠或不可靠。对于软件,典型的期望包括:

  • 应用程序执行用户所期望的功能
  • 可容忍用户出现错误或不正确的软件使用方法
  • 性能可应对典型场景、合理负载压力和数据量
  • 系统可防止任何未经授权的访问和滥用

上述目标都支持才算 “正常工作”,那么我们可以认为可靠性代表:即使发生某些错误,系统仍可继续正常工作。

可能出错的事情称为错误( fault )或故障,系统可应对错误称为容错(fault tolerant)或弹性(resilient)。 前面那个词略显误导 :似乎暗示系统可容忍各种可能的故障类型,这显然不可能。夸张的,如果整个地球(及其上的所有服务器)都被黑洞吞噬,要在这个级别容错就意味着必须在宇宙范围内进行系统冗余。试想,这将是天价预算。因此,容错总是指特定类型故障,系统才更有实际意义。

故障与失效(failure)不完全一致。故障通常被定义为组件偏离其正常规格,而失效意味系统作为 体停止,无法向用户提供所需服务。我们不可能将故障概率降到0,因此通常设计容错机制来避免从故障引发系统失效。

在这种容错系统,为了测试,可有意提高故障概率,例如随机杀进程,确保系统仍保持健壮。很多关键bug正是由于错误处理不当造成的。以此,持续检验、测试系统容错机制,增加对真实发生故障时应对的信心。

虽然倾向于容忍故障而非预防故障,但存在“预防胜于治疗”的情况,安全问题就是,例如若攻击者破坏系统窃取敏感数据,则该事件造成的影响显然无法撤销。而本系列针对那些影响可以被消除的故障类型。黑客大佬操作,不在谈论范围。

2.1 硬件故障

考虑系统故障时,硬件故障易想到硬盘崩溃,内存故障,停电,甚至拔网线。任何与大型数据中心合作过的人都可以告诉你,当有很多机器时,这类事情迟早发生。

研究证明硬盘平均无故障时间( MTTF )约为 10~50 年。因此,在一个包括10000 个磁盘的存储集群中,应预期平均每天有1个磁盘故障。

第一反应通常是为硬件添加冗余来减少系统故障率。 例如对磁盘配置RAID ,服务器配备双电源,甚至热插拔CPU,数据中心添加备用电源、发电机等。

当一个组件故障,冗余组件可快速接管,之后再更换失效组件。这并不能完全防止硬件故障所引发的失效,但还是普遍采用,且在实际中确实可以让系统持续运行数年。

最近,硬件冗余方案对大多数应用场景还够,使得单台机器完全失效概率降非常低。只要可以将备份迅速恢复到新机器,故障停机时间在大多数应用中并非灾难性。而多机冗余则只对少数关键应用更有意义,对于这些应用,高可用性绝对必要。

但随数据量和应用计算需求增加,更多应用可运行在大规模机器,随之来的硬件故障率呈线性增长。例如,对某些云平台(AWS),由于系统强调总体灵活性与弹性,而非单台机器可靠性,虚拟机实例会在事先无告警情况下出现无法访问问题。因此,通过软件容错来容忍多机失效,成为新方案或至少成为硬件容错的补充。这样的系统更具操作便利性,例如当需要重启时为 os 打补丁,可以每次给一个节点打补丁后重启,无需同时下线整个系统。

硬件故障之间一般相互独立:一台机器的磁盘故障不意味着另一台机器的磁盘也失效。除非存在某种弱相关(例如共性原因,服务器机架温度过高),不然不太会大量硬件组件同时失效。

2.2 软件错误

系统内的软件问题。这些故障事先更加难以预料,而且因为节点之间由软件关联,因而往往会导致更多系统故障,如:

  • 由于软件错误,导致当输入特定值时,应用服务器总崩溃。 如 2012.6.30发生闰秒,由于Linux内核中的bug ,导致了很多应用程序在该时刻发生挂起
  • 应用进程使用了某些共享资源如CPU 、内存、磁盘或网络带宽 ,但却不幸失控跑飞了
  • 系统依赖于某些服务,但该服务突然变慢,甚至无响应或开始返回异常响应
  • 级联故障,其中某组件的小故障触发另一个组件故障,进而引发更多系统问题

导致软件故障的bug通常会长时间处于引而不发的状态,直到碰到特定触发条件。这意味着系统软件其实对使用环境存在某种假设,而这种假设多数情况都可以满足,但特定情况下,假设不再成立。

软件系统问题有时无快速解决办法,只能仔细考虑很多细节,包括认真检查依赖的假设与系统之间的交互 ,进行全面测试,进程隔离,允许进程崩溃并自动重启,反复评估,监控并分析生产环节的行为表现等。如果系统提供某些保证,例如,MQ 的输出消息数量=输入消息的数量, 则能不断检查确认,若发现差异则告警。

2.3 人为失误

设计和构建软件系统是由人完成, 也由人运维,是人就无法做到万无一失。例如,一项针对大型互联网服务的调查发现,运维者的配置错误居然是系统下线的首要原因,而硬件问题(服务器或网络)仅在10%~25%的故障中有所影响。

假定人不可靠,那如何保证系统可靠性呢?可尝试结合如下方案:

  • 以最小出错方式设计系统
    例如,精心设计的抽象层、 API 以及管理界面,使“做正确的事”很轻松,但搞坏很复杂。但若限制过多,人们就会想法绕过它,这会抵消其正面作用。因此根本还是 trade off
  • 尽力分离最易出错的地方、易引发故障的接口
    尤其是提供一个功能齐全但非生产用的沙箱环境,让人能放心尝试、体验,包括导人真实数据,即使故障,也不会影响真实用户。
  • 充分测试,从各单元测试到全系统集成测试以及手动测试
    自动化测试已被广泛使用,对于覆盖正常操作中很少出现的边界条件等很重要
  • 出现人为失误时,提供快速的恢复机制以减少故障影响。例如,快速回滚配置改动,滚动发布新代码(这样任一错误仅影响小部分用户),并提供校验数据的工具(防止旧的计算方式错误)
  • 设置详细清晰的监控子系统,包括性能指标和错误率。在其他行业称为遥测(Telemetry), 一旦火箭离地,遥测对跟踪运行和了解故障很重要。监控能给我们发送告警信号,并检查是否存在假设不成立或违反约束条件等。这些检测指标对诊断问题很有用。
  • 推行管理流程并定期培训 重要且复杂,看每个团队管理者自己规划。

2.4 可靠性的重要性

可靠性不仅针对核电站和空中交管软件系统,很多应用都得可靠工作。商业软件的错误会导致效率下降(如数据报告错误,甚至带来法律风险),电商网站暂停对营收和声誉都带来巨大损失。

即使“非关键”应用,也应秉持对用户负责态度。例如一对父母,将其所有照片及他们孩子的视频存放在你的照片应用中。 如果不幸发生数据库损坏,他们怎么想?会存在其他一些情况,例如面对不太确定的市场开发原型系统或服务的利润微薄,有时也会牺牲一点可靠性来降低开发成本或运营开销,只能说请三思而后行。