第4章 可用性

技术并不总是与完美和可靠性相契合。 实际上,二者相差甚远!

——让-米歇尔·雅尔(Jean-Michel Jarre)

可用性是指软件的一种特性,即当你需要软件执行任务时,它能够正常运行并随时准备执行任务。这是一个宽泛的概念,涵盖了通常所说的可靠性(尽管它可能还包含诸如定期维护导致的停机时间等其他考量因素)。可用性基于可靠性的概念,增加了恢复这一概念,也就是说,当系统出现故障时,它能够自我修复。正如我们将在本章中看到的那样,修复可以通过多种方式实现。

可用性还包括系统屏蔽或修复故障的能力,使故障不至于演变成失效,从而确保在特定时间间隔内累计服务中断时长不超过规定值。这一定义包含了可靠性、健壮性以及任何涉及不可接受失效概念的其他质量属性相关概念。

失效是指系统偏离其规格说明的情况,且这种偏离是外部可见的。要确定失效已经发生,需要环境中有外部观察者来判断。

失效的原因被称为 “故障”。故障可以是所考虑系统的内部故障,也可以是外部故障。从故障发生到失效发生之间的中间状态被称为错误。故障可以被预防、容忍、排除或预测。通过这些措施,系统能够对故障具备 “弹性”。我们所关注的方面包括系统故障如何被检测、系统故障可能多频繁地发生、故障发生时会出现什么情况、允许系统停止运行多长时间、故障或失效在什么情况下可以安全发生、如何预防故障或失效以及失效发生时需要什么样的通知等。

可用性与安全性密切相关,但又明显有别。拒绝服务攻击的明确目的就是使系统失效,也就是让系统变得不可用。可用性也与性能密切相关,因为可能很难判断系统是已经失效了,还是仅仅响应极其缓慢。最后,可用性与安全性紧密相连,安全性关注的是防止系统进入危险状态,以及在系统进入危险状态时进行恢复或限制损害。

构建高可用性容错系统时,最具挑战性的任务之一就是了解系统运行过程中可能出现的失效的性质。一旦了解了这些,就可以在系统中设计相应的缓解策略。

由于系统失效是用户可观察到的,修复时间就是直到失效不再能被观察到所花费的时间。这可能是用户响应时间中难以察觉的延迟,也可能是某人要飞到安第斯山脉的偏远地区去维修一台采矿机械所花费的时间(这是一位负责维修采矿机引擎软件的人员向我们讲述的事例)。这里 “可观察性” 的概念至关重要:如果一个失效 “本可以被观察到”,那么它就是失效,无论实际上是否被观察到。

此外,我们通常还关注系统在发生失效后仍保留的功能水平,即降级运行模式。

区分故障和失效使我们能够讨论修复策略。如果包含故障的代码被执行了,但系统能够从故障中恢复,且没有出现与原本规定行为有任何可观察到的偏离,我们就说没有发生失效。

系统的可用性可以通过在特定时间间隔内,系统在规定界限内提供规定服务的概率来衡量。有一个广为人知的表达式可用于推导稳态可用性(该表达式源于硬件领域):

MTBF / (MTBF + MTTR)

其中,MTBF指平均故障间隔时间,MTTR指平均修复时间。在软件领域,应该这样解读这个公式:在考虑可用性时,应当思考是什么会导致系统出现故障、此类事件发生的可能性有多大以及修复故障需要多长时间。

依据这个公式,可以计算概率,并做出诸如 “系统具备 99.999% 的可用性” 或者 “系统在需要时无法运行的概率为 0.001%” 之类的论断。在计算可用性时,不应考虑计划内停机时间(即系统有意停止服务的时间),因为那时系统被视为 “不需要运行”;当然,这取决于系统的具体需求,这些需求通常会在服务水平协议(SLA)中明确规定。这可能会导致一些看似奇怪的情况,比如系统停机了,用户正在等待其恢复运行,但由于停机是计划内的,所以不会计入任何可用性要求的考量范围。

检测到的故障在上报和修复之前可以进行分类。这种分类通常基于故障的严重程度(严重、较严重或轻微)以及对服务的影响(影响服务或不影响服务)。它能为系统操作员提供及时且准确的系统状态信息,并便于采用恰当的修复策略。修复策略可能是自动化的,也可能需要人工干预。

如前文所述,系统或服务所期望的可用性通常会通过服务水平协议(SLA)来表述。服务水平协议明确了所保证的可用性级别,并且通常还规定了如果违反该协议,服务提供商将受到的惩罚。例如,亚马逊为其亚马逊弹性计算云(EC2)服务提供了如下服务水平协议:

亚马逊网络服务(AWS)将尽商业上合理的努力,使所包含的各项服务在每个亚马逊网络服务区域内每月的正常运行时间百分比至少达到 99.99%,在每个月度计费周期内均如此(即 “服务承诺”)。如果所包含的任何服务未达到服务承诺,你将有资格按如下所述获得服务补偿。

表 4.1 列举了系统可用性要求的示例以及在 90 天和 1 年观测期内可接受的系统停机时间的相关阈值。“高可用性” 一词通常指以 99.999%(“五个 9”)或更高可用性为目标的设计。如前文所述,只有非计划内的停机才会计入系统停机时间。

表 4.1 系统可用性需求

可用性 停机时间/90 天 停机时间/年
99.0% 21小时36分 3天15.6小时
99.9% 2小时10分 8小时45分
99.99% 12分58秒 52分34秒
99.999% 1分18秒 5分15秒
99.9999% 8秒 32秒

4.1 可用性通用场景

现在我们可以对 表 4.2 中所总结的可用性通用场景的各个部分进行描述。

表 4.2 可用性通用场景

场景部分 描述 可能的值
来源 这规定了故障的来源。 内部/外部:人员、硬件、软件、物理基础设施、物理环境
触发事件 可用性场景中的触发事件是故障。 故障:遗漏、崩溃、时间错误、响应错误
构件 这明确了系统的哪些部分应对该故障负责以及会受到该故障的影响。 处理器、通信通道、存储设备、进程、系统环境中受影响的构件。
环境 我们可能不仅对系统在其“正常”环境中的运行表现感兴趣,而且对它在诸如已经从故障中恢复时等情况下的运行表现也感兴趣。 正常运行、启动、关机、修复模式、降级运行、过载运行
响应 最常见的期望响应是防止故障演变成失效,但其他响应可能也很重要,比如通知相关人员或记录故障以便后续分析。本节明确了期望的系统响应。 防止故障演变成失效
- 检测故障:
- 记录故障
- 通知相关实体(人员或系统)
- 从故障中恢复
- 禁用导致故障的事件源
- 在进行修复时暂时不可用
- 修复或屏蔽故障/失效,或控制其造成的损害
- 在进行修复时以降级模式运行
- 系统必须可用的时间或时间区间
响应度量 根据所提供服务的关键程度,我们可能会重点关注多项可用性指标。 - 可用性百分比(例如,99.999%)
- 故障检测时间
- 故障修复时间
- 系统可处于降级模式的时间或时间区间
- 系统能够预防或在不出现失效的情况下处理某类故障的比例(例如,99%)或速率(例如,每秒最多100次)

图4.1展示了一个源自表4.2中通用场景的具体可用性场景示例。该场景如下:服务器集群中的一台服务器在正常运行期间出现故障,系统会通知操作员,并且能够持续运行,无停机时间

A sample concrete availability scenario is presented.

图 4.1 具体可用性场景示例

4.2 可用性策略

当系统不再提供与其规格说明相符的服务,且这种失效情况能被系统相关参与者观察到时,就会发生失效。故障(或多个故障的组合)有可能导致失效。相应地,可用性策略旨在使系统能够预防或承受系统故障,从而使系统所提供的服务始终符合其规格要求。我们在本节中讨论的策略将防止故障演变成失效,或者至少限制故障的影响,并使修复成为可能,如 图 4.2 所示。

The tactics to control response diagram is presented. The stimulus is the fault. The response is the fault masked, prevented, or repair made.

图 4.2 可用性策略的目标

可用性策略有三个目的之一:故障检测、故障恢复或故障预防。可用性策略如 图 4.3 所示。这些策略通常会由软件基础设施(如中间件包)来提供,所以作为架构师,你的工作可能是选择并评估(而非实现)正确的可用性策略以及策略的正确组合。

A flowchart of the availability tactics.

图 4.3 可用性策略

检测故障

在任何系统能够针对故障采取行动之前,必须先检测到故障的存在或者对其有所预见。这一类别的策略包括:

  • 监控(Monitor):该组件用于监控系统中其他各个部分的健康状态,包括处理器、进程、输入 / 输出、内存等等。系统监控器能够检测网络或其他共享资源中的失效或拥塞情况,例如检测是否遭受拒绝服务攻击。它会协调使用此类别的其他策略的软件,以检测出现故障的组件。例如,系统监控器可以启动 “自检(self-tests)”,或者成为检测有故障的 “时间戳(timestamps)” 或丢失的 “心跳(heartbeats)” 的组件。1
  • Ping / echo:在此策略中,节点之间会交换异步请求 / 响应消息对,用于确定可达性以及通过相关网络路径的往返延迟。此外,echo表明被 Ping 的组件处于运行状态。Ping 通常由系统监控器发送。Ping / echo 需要设置一个时间阈值,该阈值告知发送 Ping 的组件在判定被 Ping 的组件出现故障(“超时”)之前需要等待echo的时长。通过互联网协议(IP)互联的节点有标准的 Ping / echo 实现方式。
  • 心跳(Heartbeat):这种故障检测机制采用系统监控器与被监控进程之间周期性的消息交换方式。心跳的一种特殊情况是,被监控的进程会定期重置其监控器中的看门狗定时器,以防止定时器到期进而发出故障信号。对于关注可扩展性的系统,可以通过将心跳消息附加到正在交换的其他控制消息上来减少传输和处理开销。心跳与 Ping / echo 的区别在于由谁负责发起健康检查 —— 是监控器还是组件自身。
  • 时间戳(Timestamp):该策略用于检测事件的不正确顺序,主要应用于分布式消息传递系统中。事件的时间戳可以通过在事件发生后立即将本地时钟的状态赋予该事件来确定。序列号也可用于此目的,因为分布式系统中的时间戳在不同处理器上可能不一致。有关分布式系统中时间主题的更全面讨论,请参见 第 17 章
  • 状态监测(Condition monitoring):该策略涉及检查进程或设备中的状态,或者验证设计过程中所做的假设。通过监测状态,此策略可防止系统产生错误行为。校验和的计算就是这种策略的常见示例。然而,监控器本身必须简单(理想情况下,应能被证明是正确的),以确保它不会引入新的软件错误。
  • 合理性检查(Sanity checking):该策略检查组件特定操作或输出的有效性或合理性。它通常基于对内部设计、系统状态或所审查信息的性质的了解。它最常用于接口处,用于检查特定信息流。
  • 表决(Voting):表决涉及对多个本应产生相同结果的来源的计算结果进行比较,如果结果不一致,则决定采用哪些结果。该策略关键取决于表决逻辑,表决逻辑通常实现为一个简单、经过严格审查和测试的单例,以便降低出错概率。表决还关键取决于要有多个可评估的来源。典型方案如下:
    • 副本(Replication):这是表决的最简单形式,在此形式下,各组件彼此完全相同。拥有多个相同组件的副本对于防范硬件的随机故障可能很有效,但无法防范硬件或软件中的设计或实现错误,因为该策略中没有嵌入任何形式的多样性。
    • 功能冗余(Functional redundancy):相反,该策略旨在通过实现设计多样性来解决硬件或软件组件中的共模故障问题(即副本由于共享相同的实现而同时出现相同故障)。此策略试图通过为冗余增加多样性来应对设计故障的系统性本质。在给定相同输入的情况下,功能冗余组件的输出应该相同。功能冗余策略仍然容易受到规格说明错误的影响 —— 当然,开发和验证功能副本的成本也会更高。
    • 解析冗余(Analytic redundancy):该策略不仅允许组件私有部分之间存在多样性,还允许组件的输入和输出之间存在多样性。此策略旨在通过使用独立的需求规格说明来容忍规格说明错误。在嵌入式系统中,当某些输入源有时可能不可用时,解析冗余会有所帮助。例如,航空电子程序有多种计算飞机高度的方法,比如利用气压、雷达高度计以及通过几何方法(利用地面前方某点的直线距离和俯视角)来计算。与解析冗余一起使用的表决机制需要比简单的多数决定或计算简单平均值更为复杂。它可能需要了解哪些传感器当前是可靠的(或不可靠的),并且可能需要通过随时间对各个值进行混合和平滑处理来生成比任何单个组件所能提供的更高保真度的值。
  • 异常检测(Exception detection):该策略侧重于检测改变正常执行流程的系统状况。它可进一步细化如下:
    • 系统异常(System exceptions):会因所采用的处理器硬件架构不同而有所变化。它们包括诸如除以零、总线和地址故障、非法程序指令等故障。
    • 参数边界(Parameter fence):该策略在对象的任何可变长度参数之后立即放置一个已知的数据模式(如 0xDEADBEEF),这样就可以在运行时检测到对分配给对象可变长度参数的内存的覆盖情况。
    • 参数类型(Parameter typing):采用一个基类,该基类定义了用于添加、查找以及遍历类型 - 长度 - 值(TLV)格式消息参数的函数。派生类使用基类函数来提供构建和解析消息的函数。参数类型的使用确保消息的发送方和接收方就内容类型达成一致,并能检测出双方不一致的情况。
    • 超时(Timeout):该策略是指当一个组件检测到它自身或另一个组件未能满足其时间约束时就会引发异常。例如,一个正在等待另一个组件响应的组件,如果等待时间超过某个值,就可以引发异常。
  • 自检(Self-test):组件(或者更有可能是整个子系统)可以运行一些程序来测试自身是否能正确运行。自检程序可以由组件自身启动,也可以由系统监控器不时调用。这些程序可能会涉及使用状态监测中发现的一些技术,比如校验和。

从故障中恢复

“从故障中恢复” 策略可细分为准备及修复策略和重新引入策略。后者涉及将出现故障(但已修复)的组件重新引入正常运行状态。

准备及修复策略基于重试计算或引入冗余的多种组合方式:

  • 冗余备件:该策略指的是一种配置,即如果主组件出现故障,一个或多个备用组件可以介入并接管工作。此策略是热备、温备和冷备模式的核心,这些模式的主要区别在于备份组件在接管时其数据的更新程度。

  • 回滚:回滚允许系统在检测到失效时恢复到之前已知的良好状态(称为 “回滚线”)—— 即时光倒流。一旦达到良好状态,执行就可以继续。该策略通常与事务策略以及冗余备件策略相结合,以便在回滚发生后,将故障组件的备用版本提升为活动状态。回滚依赖于正在回滚的组件能够获取之前良好状态的副本(检查点)。检查点可以存储在固定位置,并按固定间隔更新,或者在处理过程中的方便或关键时间点进行更新,比如在完成一项复杂操作时。

  • 异常处理:一旦检测到异常,系统将以某种方式对其进行处理。最简单的做法就是直接崩溃 —— 但当然,从可用性、易用性、可测试性以及常理角度来看,这是个糟糕的主意。还有更有效的处理方式。所采用的异常处理机制在很大程度上取决于所使用的编程环境,从简单的函数返回代码(错误代码)到使用包含有助于故障关联信息(如异常名称、异常来源和异常原因)的异常类不等。软件随后可以利用这些信息来屏蔽或修复故障。

  • 软件升级:该策略的目标是以不影响服务的方式对可执行代码镜像进行在线升级。相关策略包括以下几种:

    • 函数补丁:这种在过程式编程中使用的补丁,利用增量链接器 / 加载器将更新后的软件函数存储到目标内存的预分配段中。软件函数的新版本将使用被弃用函数的入口和出口点。
    • 类补丁:这种升级适用于执行面向对象代码的目标,其中类定义包含一种后门机制,可实现在运行时添加成员数据和函数。
    • 无损在线软件升级(ISSU):这借助冗余备件策略来实现对软件及相关架构不影响服务的升级。

    在实际应用中,函数补丁和类补丁用于修复漏洞,而无损 ISSU 则用于提供新特性和功能。

  • 重试:重试策略假定导致失效的故障是临时性的,重新尝试操作可能会成功。它常用于网络和服务器集群中,这些地方预计会出现失效且失效较为常见。在判定为永久性失效之前,应当对尝试重试的次数设置限制。

  • 忽略故障行为:当我们确定来自特定来源的消息是虚假的时,该策略要求忽略这些消息。例如,我们希望忽略来自传感器实时失效产生的消息。

  • 优雅降级:该策略在组件出现失效的情况下维持最关键的系统功能,同时舍弃不太关键的功能。这适用于个别组件失效会平稳降低系统功能,而非导致整个系统失效的情况。

  • 重新配置:重新配置试图通过将职责重新分配给仍在运行的(可能有限的)资源或组件,同时尽可能维持更多功能,以此从失效中恢复。

重新引入策略,当一个出现故障的组件在修复后被重新引入时,就会涉及重新引入策略。重新引入策略包括以下几种:

  • 影子模式:该策略指的是让之前出现故障或进行了在线升级的组件在恢复到活动角色之前,以 “影子模式” 运行一段预先定义的时间。在此期间,可以对其行为的正确性进行监控,并且它可以逐步重新填充自身状态。
  • 状态再同步:这种重新引入策略是冗余备件策略的辅助策略。当与主动冗余(冗余备件策略的一种形式)一起使用时,由于主动和备用组件各自并行接收并处理相同的输入,状态再同步会自然发生。实际上,主动和备用组件的状态会定期进行比较以确保同步。这种比较可能基于循环冗余校验计算(校验和),或者对于提供安全关键服务的系统,基于消息摘要计算(单向哈希函数)。当与冗余备件策略的被动冗余版本一起使用时,状态再同步仅仅基于从主动组件定期向备用组件传输的状态信息,通常是通过检查点的方式。
  • 逐步重启:这种重新引入策略允许系统通过改变重启组件的粒度并尽量减小对服务的影响程度,从而从故障中恢复。例如,考虑一个支持四级重启(编号为 0 - 3)的系统。最低级别的重启(0 级)对服务的影响最小,采用被动冗余(温备)方式,即故障组件的所有子线程会被终止并重新创建。这样,只有与子线程相关的数据会被释放并重新初始化。下一级别的重启(1 级)会释放并重新初始化所有未受保护的内存,受保护的内存则不受影响。再下一级别的重启(2 级)会释放并重新初始化所有内存,包括受保护和未受保护的内存,迫使所有应用程序重新加载并重新初始化。最高级别的重启(3 级)涉及完全重新加载并重新初始化可执行镜像以及相关的数据段。对逐步重启策略的支持对于优雅降级概念尤为有用,在这种情况下,系统能够在维持对关键任务或安全关键应用程序支持的同时,降低其提供的服务级别。
  • 不间断转发:这一概念源于路由器设计,它假定功能分为两部分:管理平面或控制平面(负责管理连接性和路由信息)和数据平面(负责实际执行将数据包从发送方路由到接收方的工作)。如果路由器的活动管理组件出现故障,它可以在路由协议信息恢复和验证期间,与相邻路由器一起沿着已知路由继续转发数据包。当控制平面重启时,它会实现 “优雅重启”,在数据平面继续运行的同时逐步重建其路由协议数据库。

预防故障

与其先检测故障然后再尝试从故障中恢复,要是你的系统一开始就能防止故障发生,那会怎样呢?尽管听起来似乎需要某种预见能力,但事实证明,在很多情况下确实可以做到这一点。2

  • 停用服务:该策略指的是为了减轻潜在的系统失效,暂时将系统组件置于停用状态。例如,系统的某个组件可能会被停用并重置,以清除潜在故障(比如内存泄漏、内存碎片,或者未受保护缓存中的软错误),避免故障积累到影响服务的程度,进而导致系统失效。这一策略的其他叫法还有 “软件再生” 和 “修复性重启”。如果你每晚都重启电脑,那就是在运用停用服务这一策略。
  • 事务处理:旨在提供高可用性服务的系统利用事务语义来确保分布式组件之间交换的异步消息具备原子性、一致性、隔离性和持久性 —— 这些特性统称为 “ACID 特性”。事务处理策略最常见的实现方式是 “两阶段提交”(2PC)协议。该策略可防止因两个进程试图同时更新同一数据项而引发的竞争条件。
  • 预测模型:预测模型与监控器相结合,用于监控系统进程的健康状态,以确保系统在其正常运行参数范围内运行,并在系统接近临界阈值时采取纠正措施。所监控的运行性能指标用于预测故障的发生,示例包括会话建立速率(在 HTTP 服务器中)、阈值跨越情况(对某些受限的共享资源监控高低水位线)、进程状态统计信息(例如,正在服务、停用、维护中、空闲)以及消息队列长度统计信息。
  • 异常预防:该策略指的是用于防止系统异常发生的技术。前面已经讨论过使用异常类,它能让系统从系统异常中透明地恢复。异常预防的其他示例包括纠错码(用于电信领域)、抽象数据类型(如智能指针),以及使用包装器来防止悬空指针或信号量访问违规等故障。智能指针通过对指针进行边界检查,并确保在无数据引用资源时自动释放资源,从而避免资源泄漏,以此来预防异常。
  • 扩大能力集:程序的能力集是指它能够 “胜任” 运行的状态集合。例如,分母为零的状态超出了大多数除法程序的能力集范围。当一个组件抛出异常时,这意味着它发现自己处于能力集之外了;本质上就是它不知道该怎么做,于是放弃了。扩大组件的能力集意味着将其设计为能在正常运行过程中处理更多情况 —— 也就是故障。例如,一个假定能够访问共享资源的组件,如果发现访问被阻止,可能就会抛出异常。而另一个组件可能只是等待访问,或者立即返回并提示下次能够访问时会自行完成操作。在这个例子中,第二个组件的能力集比第一个组件的更大。

4.3 基于可用性策略的调查问卷

基于 4.2 节 中所述的策略,我们可以创建一组受可用性策略启发的问题,如 表 4.3 所示。为了全面了解为支持可用性而做出的架构选择,分析人员会提出每个问题,并将答案记录在表格中。然后,这些问题的答案可成为进一步活动的重点,例如文档调研、代码或其他构件分析、代码逆向工程等等。

表 4.3 基于可用性策略的调查问卷

策略组 策略问题 支持与否(是/否) 风险 设计决策与定位 推理和假设
检测故障 系统是否使用Ping/echo来检测组件或连接失效,又或者网络拥塞情况?        
  系统是否使用某个组件来监控系统其他部分的健康状态?系统监控器能够检测网络或其他共享资源中的失效或拥塞情况,例如检测是否遭受拒绝服务攻击。        
  系统是否使用心跳(heartbeat)机制(即系统监控器与某个进程之间周期性的消息交换)来检测组件或连接失效,又或者网络拥塞情况?        
  系统是否使用时间戳(timestamp)来检测分布式系统中事件的不正确顺序?        
  系统是否使用表决(voting)机制来检查复制的组件是否产生相同的结果?这些复制的组件可能是完全相同的副本、功能冗余组件,也可能是解析冗余组件。        
  系统是否使用异常检测来检测改变正常执行流程的系统状况(例如系统异常、参数边界、参数类型、超时)?        
  系统能否进行自检以检测自身是否能正常运行?        
从故障中恢复(准备及修复) 系统是否采用冗余备件?组件作为活动组件还是备用组件的角色是固定的,还是会在出现故障时发生变化?切换机制是什么?切换的触发条件是什么?备用组件承担其职责需要多长时间?        
  系统是否采用异常处理机制来应对故障?通常,这种处理涉及报告、纠正或屏蔽故障。        
  系统是否采用回滚机制,以便在出现故障时能够恢复到先前保存的良好状态(“回滚线”)?        
  系统能否以不影响服务的方式对可执行代码镜像进行在线软件升级        
  在组件或连接失效可能是临时性的情况下,系统是否会有计划地进行重试        
  系统能否直接忽略故障行为(例如,当确定某些消息是虚假消息时,忽略这些消息)?        
  当资源受损时,系统是否有降级策略,即在组件出现失效的情况下维持最关键的系统功能,同时舍弃不太关键的功能?        
  系统在出现失效后是否有一致的重新配置策略和机制,在尽可能维持更多功能的同时,将职责重新分配给仍在运行的资源?        
从故障中恢复(重新引入) 系统能否在将先前出现故障或进行了在线升级的组件恢复到活动角色之前,让该组件以“影子”模式运行一段预定义的时间?        
  如果系统采用主动或被动冗余机制,它是否也会使用状态再同步功能,将状态信息从活动组件发送到备用组件呢?        
  系统是否采用逐步升级式重启,通过改变重启组件的粒度并尽量减小受影响的服务级别,从而从故障中恢复?        
  系统的消息处理和路由部分能否采用不间断转发,即将功能划分为管理平面和数据平面?        
预防故障 系统能否使组件停止服务,即为了预防潜在的系统失效,暂时将系统组件置于停用状态?        
  系统是否采用事务机制——将状态更新进行捆绑,以便分布式组件之间交换的异步消息具备“原子性”“一致性”“隔离性”和“持久性”?        
  系统是否使用预测模型来监测组件的健康状态,以确保系统在正常参数范围内运行?当检测到预示未来可能出现故障的状况时,该模型会启动纠正措施。        

4.4 可用性模式

本节介绍一些最为重要的可用性架构模式。

前三种模式均围绕冗余备件策略展开,将一并进行描述。它们的主要区别在于备份组件的状态与活动组件状态相匹配的程度(当组件是无状态时属于一种特殊情况,此时前两种模式是相同的)。

  • 主动冗余(热备用):对于有状态组件,这是指一种配置方式,在保护组 3 中的所有节点(活动节点或冗余备用节点)并行接收并处理相同的输入,使得冗余备用节点能够与活动节点保持同步状态。由于冗余备用节点的状态与活动处理器的状态完全相同,它可以在数毫秒内接管出现故障的组件。一个活动节点和一个冗余备用节点这种简单情况通常被称为 “一加一冗余”。主动冗余也可用于设施保护,通过使用活动和备用网络链路来确保高可用性的网络连接。

  • 被动冗余(温备用):对于有状态组件,这是指一种配置方式,在这种配置下,只有保护组中的活动成员处理输入流量。它们的职责之一是定期向冗余备用节点提供状态更新。由于冗余备用节点所维护的状态与保护组中活动节点的状态只是松散耦合(耦合的松散程度取决于状态更新的周期),所以这些冗余节点被称为温备用节点。被动冗余提供了一种解决方案,在可用性更高但计算资源消耗更大(且成本更高)的主动冗余模式和可用性较低但复杂度明显更低(且成本也明显更低)的冷备用模式之间实现了一种平衡。

  • 备用(冷备用):冷备用是指一种配置方式,在这种配置下,冗余备用节点在故障转移发生之前一直处于停用状态,在将其投入使用之前,需要对冗余备用节点执行加电复位 4 程序。由于其恢复性能较差,因而平均修复时间较长,这种模式不太适合有高可用性要求的系统。

    优势:

    • 冗余备用的优势在于,在出现失效时,系统仅经过短暂延迟后就能继续正常运行。与之相对的情况是,系统会停止正常运行,或者完全停止运行,直至故障组件被修复,而这一修复过程可能需要数小时或数天时间。

    权衡:

    • 采用这些模式中的任何一种都需要在提供备用组件方面付出额外的成本并增加复杂性。
    • 这三种模式之间的权衡在于从故障中恢复所需的时间与为使备用组件保持最新状态而产生的运行时成本之间的对比。例如,热备用成本最高,但恢复时间最快。

    其他可用性模式包括以下几种:

  • 三模冗余(TMR):这是一种广泛应用的表决策略实现方式,它采用三个执行相同任务的组件。每个组件接收相同的输入,并将其输出转发给表决逻辑,表决逻辑会检测三个输出状态之间的任何不一致情况。面对不一致情况时,表决器会报告故障,它还必须决定使用哪个输出,而且该模式的不同实例会采用不同的决策规则。典型的选择是遵循多数原则或者选择对不同输出进行某种计算得出的平均值。

    当然,采用 5 个、19 个或 53 个冗余组件的该模式的其他版本也是可行的。不过,在大多数情况下,3 个组件足以确保可靠的结果。

    优势:

    • 三模冗余易于理解和实现。它完全不受可能导致不同结果的因素影响,只关心做出合理选择,以便系统能够继续运行。

    权衡:

    • 在增加副本数量(这会增加成本)和所获得的可用性之间需要进行权衡。在采用三模冗余的系统中,两个或更多组件同时出现故障的统计概率极小,3 个组件在可用性和成本之间达到了一个理想的平衡点。
  • 断路器:一种常用的可用性策略是重试。在调用服务时出现超时或故障的情况下,调用者会不断地重试。断路器可防止调用者无休止地尝试,避免其一直等待永远不会到来的响应。通过这种方式,当它判定系统正在处理故障时,就会打破无休止的重试循环。这就是提示系统开始处理故障的信号。在断路器 “复位” 之前,后续的调用会立即返回,而不会传递服务请求。

    优势:

    • 这种模式可以让各个组件无需再制定在判定故障前允许重试多少次的策略。
    • 最糟糕的情况是,无休止的无效重试会使调用组件变得和出现故障的被调用组件一样毫无用处。这个问题在分布式系统中尤为突出,在分布式系统中,可能会有许多调用者调用一个无响应的组件,结果自身实际上也停止服务,导致故障在整个系统中连锁反应。断路器与监听它并启动恢复程序的软件协同工作,可防止出现这种问题。

    权衡:

    • 在选择超时(或重试)值时必须谨慎。如果超时时间过长,就会增加不必要的延迟。但如果超时时间过短,断路器就会在不必要的时候触发 —— 这属于一种 “误报” 情况,会降低这些服务的可用性和性能。

    其他常用的可用性模式还包括以下几种:

  • 进程对:这种模式采用检查点和回滚机制。在出现故障的情况下,备份进程一直在设置检查点,并(如有必要)回滚到安全状态,以便在失效发生时能够随时接管。

  • 前向纠错恢复:这种模式提供了一种通过 “向前推进” 到理想状态来摆脱不良状态的方法。这通常依赖于内置的纠错能力,比如数据冗余,这样就可以在无需回退到先前状态或重试的情况下纠正错误。前向纠错恢复会找到一个安全的、可能是降级的状态,以便操作能够继续向前推进。

4.5 扩展阅读

可用性模式

  • 你可以在 [Hanmer 13] 中阅读有关容错模式的内容。

可用性的通用策略

  • 本章中部分可用性策略的更详细讨论见 [Scott 09]。本章的大部分内容取材于此。
  • 互联网工程任务组(IETF)已经颁布了多项支持可用性策略的标准。这些标准包括《不间断转发》[IETF 2004]、《Ping / Echo》(《网际控制报文协议》[IETF 1981] 或《网际控制报文协议第 6 版》[RFC 2006b]《Echo Request/Response》)以及多协议标签交换(标签交换路径 Ping)网络 [IETF 2006a]。

可用性策略 —— 故障检测

  • 三模冗余(TMR)由莱昂斯在 20 世纪 60 年代早期提出 [Lyons 62]。
  • 表决策略中的故障检测基于冯・诺依曼对自动机理论的基础性贡献,他展示了如何用不可靠的组件构建具有规定可靠性的系统 [Von Neumann 56]。

可用性策略 —— 故障恢复

  • 在开放系统互连(OSI)七层模型的物理层 [Bellcore 98, 99; Telcordia 00] 以及网络 / 链路层 [IETF 2005],都存在基于标准的主动冗余实现方式用于保护网络链路(即设施)。
  • [Nygard 18] 给出了一些系统如何因使用而降级(性能衰退)的示例。
  • 关于参数类型方面已经有大量的论文发表,不过 [Utas 05] 是在可用性的背景下(相对于其通常所在的防错背景而言)对其进行论述的。[Utas 05] 还写过有关逐步升级式重启的内容。
  • 硬件工程师经常使用准备及修复策略。例如差错检测与纠正(EDAC)编码、前向纠错(FEC)以及时间冗余。差错检测与纠正编码通常用于保护高可用性分布式实时嵌入式系统中的控制内存结构 [Hamming 80]。相反,前向纠错编码通常用于从外部网络链路中出现的物理层差错中恢复 [Morelos-Zaragoza 06].。时间冗余涉及在超过要容忍的任何瞬态脉冲宽度的时间间隔内对空间冗余的时钟或数据线进行采样,然后剔除检测到的任何缺陷 [Mavis 02]。

可用性策略 —— 故障预防

  • 帕纳斯和马迪写过关于增加元素能力集的内容 [Parnas 95]。
  • 在事务策略中很重要的原子性(ACID)属性由格雷在 20 世纪 70 年代提出,并在 [Gray 93] 中进行了深入讨论。

灾难恢复

  • 灾难是诸如地震、洪水或飓风之类的事件,这类事件会摧毁整个数据中心。美国国家标准与技术研究院(NIST)明确了在发生灾难时应考虑的八种不同类型的计划,详见美国国家标准与技术研究院特别出版物 800 - 34《联邦信息系统应急规划指南》的 第 2.2 节,网址为https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-34r1.pdf。

4.6 问题讨论

1. 针对通用场景中的每一种可能应对方式,编写一组具体的可用性场景示例。

2. 为(假设的)无人驾驶汽车的软件编写一个具体的可用性场景示例。

3. 为类似微软 Word 这样的程序编写一个具体的可用性场景示例。

4. 冗余是实现高可用性的关键策略。查看本章所介绍的模式和策略,确定其中有多少利用了某种形式的冗余,又有多少没有利用。

5. 可用性与可修改性和可部署性之间如何权衡?对于一个要求每周 7 天、每天 24 小时不间断运行(即永远不存在计划内或计划外停机时间)的系统,你会如何对其进行变更?

6. 考虑故障检测策略(Ping / echo、心跳检测、系统监控、表决以及异常检测)。使用这些策略会对性能产生哪些影响?

7. 负载均衡器(见 第 17 章)在检测到某个实例出现失效时会使用哪些策略?

8. 查阅恢复点目标(RPO)和恢复时间目标(RTO),并解释在使用回滚策略时如何利用它们来设置检查点间隔。



  1. 当检测机制是通过一个定期重置的计数器或计时器来实现时,这种系统监控的特定形式被称作“看门狗”。在正常运行期间,被监控的进程会定期重置“看门狗”计数器/计时器,以此作为其正常工作的信号的一部分;这有时被称为“喂狗”。 

  2. 这些策略涉及在运行时采取措施来预防故障发生。当然,预防故障的一个绝佳方法——至少对于你正在构建的系统而言(如果不考虑你的系统必须与之交互的其他系统的话)——就是生成高质量的代码。这可以通过代码审查、结对编程、扎实的需求评审以及许多其他良好的工程实践来实现。 

  3. 保护组是一组处理节点,其中一个或多个节点是“活动”的,其余节点作为冗余备用节点。 

  4. 上电复位可确保设备以已知状态开始运行。