

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 了解可用性
<a name="understanding-availability"></a>

 可用性是我们定量衡量韧性的主要方法之一。我们将可用性 *A* 定义为工作负载可供使用的时间百分比。这是其预期“正常运行时间”（可用时间）与衡量的总时间（预期“正常运行时间”加上预期“停机时间”）的比率。

![\[公式图片。A = 正常运行时间/（正常运行时间 + 停机时间）\]](http://docs.aws.amazon.com/zh_cn/whitepapers/latest/availability-and-beyond-improving-resilience/images/availability.png)


 为了更好地理解这个公式，我们来分析一下如何衡量正常运行时间和停机时间。首先，我们需要知道工作负载能持续多长时间不出现故障。我们称之为*平均故障间隔时间* (MTBF)，即工作负载开始正常运行与下一次故障之间的平均时间。然后，我们需要知道发生故障后需要多长时间才能恢复。

 我们称之为*平均修复（或恢复）时间* (MTTR)，即在发生故障的子系统被修复或恢复服务时，工作负载不可用的时长。MTTR 中的一个重要时间段是*平均检测时间* (MTTD)，即从故障发生到修复操作开始之间的时间长度。下图显示了所有这些指标之间的关联。

![\[MTTD、MTTR 和 MTBF 关系图\]](http://docs.aws.amazon.com/zh_cn/whitepapers/latest/availability-and-beyond-improving-resilience/images/availability-metrics.png)


 因此，我们可以使用 MTBF（工作负载运行的时间）和 MTTR （工作负载关闭的时间）来表示可用性 *A*。

![\[公式图片。A = MTBF / ( MTBF + MTTR)\]](http://docs.aws.amazon.com/zh_cn/whitepapers/latest/availability-and-beyond-improving-resilience/images/equation2.png)


 而工作负载“停机”（即不可用）的概率就是发生故障的概率 *F*。

![\[公式图片。F = 1-A\]](http://docs.aws.amazon.com/zh_cn/whitepapers/latest/availability-and-beyond-improving-resilience/images/equation3.png)


[可靠性](https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/reliability.html)是指工作负载收到请求后在指定响应时间内执行正确操作的能力。这是可用性衡量的对象。降低工作负载故障频率（提高 MTBF）或缩短修复时间（缩短 MTTR）都可以提高可用性。

**规则 1**  
降低故障频率（提高 MTBF）、缩短故障检测时间（缩短 MTTD）和缩短修复时间（缩短 MTTR）是提高分布式系统可用性的三项因素。

**Topics**
+ [分布式系统可用性](distributed-system-availability.md)
+ [可用性和依赖项](availability-with-dependencies.md)
+ [可用性和冗余](availability-with-redundancy.md)
+ [CAP 定理](cap-theorem.md)
+ [容错能力和故障隔离](fault-tolerance-and-fault-isolation.md)

# 分布式系统可用性
<a name="distributed-system-availability"></a>

 分布式系统由软件组件和硬件组件组成。有些软件组件本身可能是另一个分布式系统。底层硬件和软件组件的可用性会影响工作负载的可用性。

 使用 MTBF 和 MTTR 来计算可用性的方法起源于硬件系统。但是，分布式系统发生故障的原因与硬件故障原因截然不同。制造商始终可以计算出硬件组件失效前的平均时间，但分布式系统的软件组件却无法进行这种测试。硬件通常遵循“浴缸式”故障率曲线，而软件因为每次发布新版本时都会引入额外的缺陷，所以会形成交错式曲线（参见[软件可靠性](https://users.ece.cmu.edu/~koopman/des_s99/sw_reliability)）。

![\[硬件和软件故障率图表\]](http://docs.aws.amazon.com/zh_cn/whitepapers/latest/availability-and-beyond-improving-resilience/images/failure-rates.png)


 此外，分布式系统中软件的变化速度往往比硬件高很多。例如，标准磁性硬盘的平均年化故障率 (AFR) 可能为 0.93%，对于硬盘驱动器来说，这实际上意味着在达到失效期之前其至少有 3-5 年的使用寿命，甚至可能更长（参见 [Backblaze](https://www.backblaze.com/b2/hard-drive-test-data.html) 硬盘数据和统计，2020年）。在这段生命周期中，硬盘驱动器不会发生实质性变化，而在 3-5 年内，以亚马逊为例，其各种软件系统可能要部署 4.5 亿至 7.5 亿次更改。（参见 [Amazon Builders's Library — 自动实现无需干预的安全部署](https://aws.amazon.com/about-aws/whats-new/2020/06/new-abl-article-automating-safe-hands-off-deployments/)。） 

 硬件还存在计划性报废的概念，即具有设定的使用寿命，需要在一定时间后更换。（参见[灯泡大阴谋](https://spectrum.ieee.org/tech-history/dawn-of-electronics/the-great-lightbulb-conspiracy)。） 而软件在理论上不受这一限制，它没有失效期，可以无限期地运行。

 所有这些都意味着用于确定硬件 MTBF 和 MTTR 的各种测试和预测模型并不适用于软件。自 20 世纪 70 年代以来，人们数百次尝试通过建立模型来解决这个问题，但一般都没有脱离预测建模和估计建模的范畴。（参见[软件可靠性模型列表](https://en.wikipedia.org/wiki/List_of_software_reliability_models)。） 

 因此，要计算分布式系统未来的 MTBF 和 MTTR，从而确定未来的可用性，我们始终需要依赖某种类型的预测。我们可以通过预测建模、随机仿真、历史分析或严格测试来生成计算结果，但这些计算结果并不能成为正常运行时间或停机时间的保证。

 过去导致分布式系统出现故障的原因可能永远不会再次出现。未来造成故障的原因可能截然不同，甚至完全不可知。对于未来的故障，所需的恢复机制也可能与过去的机制不同，所花费的时间也大不相同。

 此外，MTBF 和 MTTR 均为平均值。平均值与实际值之间会有一些差异（通过标准差 σ 来衡量这种差异）。因此，在实际生产使用中，工作负载在故障与恢复之间的时间可能会更短或更长。

 但是，构成分布式系统的软件组件的可用性仍然非常重要。软件可能由于多种原因（下一节会详细介绍）而发生故障，并影响工作负载的可用性。因此，对于高可用性分布式系统来说，软件组件可用性的计算、衡量和提高应该得到与硬件和外部软件子系统同等的重视。

**规则 2**  
 工作负载中的软件可用性是决定工作负载总体可用性的一项重要因素，应与其他组件同等重视。

 值得注意的是，尽管分布式系统的 MTBF 和 MTTR 很难预测，但它们仍然可以为提高可用性提供重要信息。降低故障频率（提高 MTBF）和缩短故障发生后的恢复时间（缩短 MTTR）都可以提高实证可用性。

## 分布式系统中的故障类型
<a name="types-of-failures-in-distributed-systems"></a>

 分布式系统中通常存在两类影响可用性的错误，分别叫做*波尔错误*和*海森堡错误*（参见[“Bruce Lindsay 访谈”，ACM Queue 第 2 卷，第 8 号 – 2004 年 11 月](http://queue.acm.org/detail.cfm?id=1036486)）。

 波尔错误是可以重复出现的功能性软件问题。给定相同的输入，就能始终产生相同的错误输出（如同确定性玻尔原子模型一样，稳定并且容易检测）。工作负载进入生产环境后，这类错误非常少见。

 海森堡错误是一种短暂的错误，只发生在特定和不常见条件下。这些条件通常与硬件（例如瞬时设备故障或寄存器大小等硬件实现细节）、编译器优化和语言实现、限制条件（例如存储空间暂时不足）或竞争条件（例如不使用信号量进行多线程操作）等内容相关。

 生产环境中的大部分错误都是海森堡错误，这种错误难以捉摸，当我们尝试进行观察或调试时，它们似乎会改变行为或消失，因此很难被发现。但是，如果重新启动程序，那么失败的操作很可能会成功，因为操作环境略有不同，消除了引发海森堡错误的条件。

 因此，生产环境中的大多数故障都是暂时性的，当重试操作时，故障不太可能再次出现。为了保持韧性，分布式系统必须能够承受海森堡错误。我们将在[提高分布式系统 MTBF](increasing-mtbf.md#increasing-mtbf.title) 一节探讨如何实现这一目标。

# 可用性和依赖项
<a name="availability-with-dependencies"></a>

 在上一节中，我们提到工作负载的组件包括硬件、软件，还可能包括其他分布式系统。我们将这些组件称为*依赖项*，即工作负载为了实现功能而需要依赖的事物。依赖项包括*硬*依赖项，即工作负载要发挥作用就离不开的事物，还包括*软*依赖项，其不可用性在一段时间内可能会被忽视或容许。硬依赖项会直接影响工作负载的可用性。

 我们可能想要尝试计算工作负载的理论最大可用性。这个数值是包括软件本身在内的所有依赖项的可用性的乘积，（*α**n* 是单个子系统的可用性），因为每个依赖项都必须正常运行。

![\[公式图片。A = α1 X α2 X ... X αnsubscript>\]](http://docs.aws.amazon.com/zh_cn/whitepapers/latest/availability-and-beyond-improving-resilience/images/equation4.png)


 这种计算中使用的可用性数字通常与 SLA 或服务级别目标 (SLO) 之类的内容相关联。SLA 定义了客户将获得的预期服务级别、用于衡量服务的指标，以及未能达到服务级别时采取的补救措施或惩罚（通常是金钱）。

 使用上面的公式，我们可以得出结论：纯粹从数学上讲，工作负载的可用性不会高于其任何依赖项。但实际上，我们通常看到的情况并非如此。如果一个工作负载两个或三个具有 99.99% 可用性 SLA 的依赖项，那么工作负载本身仍然可以实现 99.99% 或更高的可用性。

 这是因为像上一节所说那样，这些可用性数字是估计值。我们会估计或预测故障发生的频率以及修复故障的速度。估计的数字并不代表一定会停机。依赖项的表现经常会超出其可用性 SLA 或 SLO 的规定。

 依赖项为发挥性能而设定的内部可用性目标也可能高于公开 SLA 中的数字。这样可以提高在发生未知或不可知情况时仍然符合 SLA 的可能性。

 最后，工作负载的依赖项可能具有不为人知的 SLA，或者不提供 SLA 或 SLO。例如，全球互联网路由是许多工作负载的常见依赖项，但我们很难知道自己全球流量正在使用哪家互联网服务提供商、他们是否具有 SLA，以及 SLA 在不同提供商之间的一致性如何。

 这一切都告诉我们，计算最大理论可用性只可能得出粗略的数量级计算结果，但其本身可能并不准确，或者无法提供有意义的见解。从数学公式中可以看出，工作负载依赖的东西越少，发生故障的总体可能性就越低。小于一的数字越少，乘积就越大。

**规则 3**  
 减少依赖项可以对可用性产生积极影响。

 数学计算还可以对依赖项选择过程起到辅助作用。选择过程会影响您如何设计工作负载、如何利用依赖项中的冗余来提高其可用性，以及您将其视为软依赖项还是硬依赖项。我们应该谨慎选择可能影响工作负载的依赖项。下一条规则提供了这方面的指导。

**规则 4**  
 通常应该选择可用性目标等于或高于工作负载目标的依赖项。

# 可用性和冗余
<a name="availability-with-redundancy"></a>

 当工作负载使用多个独立的冗余子系统时，它可以实现比使用单个子系统更高的理论可用性水平。例如，假设某个工作负载由两个完全相同的子系统组成。只要有一个子系统正常运行，工作负载就能正常运行。要让整个系统停机，两个子系统必须同时关闭。

 如果一个子系统的故障概率为 1 − *α*，则两个冗余子系统同时停机的概率就是每个子系统的故障概率的乘积：*F* = (1−*α*1) × (1−*α*2)。对于具有两个冗余子系统的工作负载，根据公式 *(3)*，其可用性为：

![\[三个公式的图片\]](http://docs.aws.amazon.com/zh_cn/whitepapers/latest/availability-and-beyond-improving-resilience/images/equation5.png)


 因此，对于两个可用性为 99% 的子系统，如果一个子系统出现故障的概率为 1%，那么两个子系统都出现故障的概率则为 (1−99%) × (1−99%) = 0.01%。这使得使用两个冗余子系统的可用性达到 99.99%。

 这一规律也适用于增加冗余备件 *s***。在公式 *(5)* 中，我们只假设有一个备件，但是一个工作负载可能有两个、三个或更多备件，从而可以在多个子系统同时失效的情况下正常运行，不会影响可用性。如果某个工作负载有三个子系统并且其中两个是备件，则所有三个子系统同时出现故障的概率为 (1−*α*) × (1−*α*) × (1−*α*)，即 (1−*α*)3。一般来说，具有 *s* 个备件的工作负载只有在 *s* \$1 1 个子系统发生故障时才会失效。

 对于具有 *n* 个子系统和 *s* 个备件的工作负载来说，*f* 代表故障次数，也是 *n* 个子系统中有 *s* \$1 1 个子系统发生故障的概率。

 这实际上是二项式定理，即从 *n* 个元素中选择 *k* 个元素的组合数学（*“**n* *选* *k**”*）。在本例中，*k* 为 *s* \$1 1。

![\[四个公式的图片\]](http://docs.aws.amazon.com/zh_cn/whitepapers/latest/availability-and-beyond-improving-resilience/images/equation6.png)


 然后，我们可以得出一个包含故障次数和备件数量的广义可用性近似值。（要理解为什么是近似值，请参阅 Highleyman 等人的著作 [Breaking the Availability Barrier](https://www.amazon.com/Breaking-Availability-Barrier-Survivable-Enterprise/dp/1410792331) 的附录 2。） 

![\[四个公式的图片\]](http://docs.aws.amazon.com/zh_cn/whitepapers/latest/availability-and-beyond-improving-resilience/images/equation7.png)


 提供独立失败的资源的任何依赖项都可以设置备件。位于不同 AWS 区域中的不同可用区或不同 Amazon S3 桶中的 Amazon EC2 实例就是一个这方面的例子。使用备件可以帮助依赖项实现更高的总体可用性，从而支持工作负载的可用性目标。

**规则 5**  
 使用备件以便提高工作负载中的依赖项的可用性。

 但是，备件需要投入成本。增加的每个备件的成本与原始模块相同，所以成本至少会线性提高。构建可以使用备件的工作负载也会增加其复杂性。工作负载必须识别依赖项故障、将工作转移到正常运行的资源上，并管理总体容量。

 冗余是一种优化问题。备件太少，工作负载可能比预期更频繁地出现故障；备件太多，工作负载的运行成本就会过高。超出某个阈值之后，增加更多备件的成本将会超过额外获得的可用性带来的收益。

 根据成本与备件的一般公式，即公式 *(7)*，如果子系统的可用性为 99.5%，设置两个备件，那么工作负载的可用性 *A* ≈ 1 − (1)(1−.995)3 = 99.9999875%（每年停机时间约为 3.94 秒）；而如果设置 10 个备件，那么可用性 *A* ≈ 1 − (1)(1−.995)11 = 25.5 个 9**（每年停机时间约为 1.26252 × 10−15 *毫**秒*，实际上等于 0）。比较这两种工作负载可以发现，每年减少四秒钟的停机时间所产生的备件成本提高了 5 倍。对于大多数工作负载来说，成本的增加与可用性的提升显然不成比例。下图显示了这种关系。

![\[增加备件导致收益递减的示意图\]](http://docs.aws.amazon.com/zh_cn/whitepapers/latest/availability-and-beyond-improving-resilience/images/effect-of-sparing.png)


 当备件不少于三个时，每年的预期停机时间不到一秒，这意味着在此之后进入了收益递减区间。有人可能想要不断增加备件以便实现更高的可用性，但实际上，成本效益很快就会消失。当子系统本身的可用性至少达到 99% 时，使用三个以上的备件并不能为几乎任何工作负载打来实质性的明显效益。

**规则 6**  
 备件的成本效益存在上限。利用最少的备件来实现所需的可用性。

 在选择正确的备件数量时，您应该考虑故障单元。例如，我们假设某个工作负载需要 10 个 EC2 实例来处理峰值容量，并且这些实例部署在单个可用区内。

 可用区是一种故障隔离边界，因此故障单元不仅仅是单个 EC2 实例，因为整个可用区内的 EC2 实例可能会一起出现故障。在这种情况下，您需要[通过另一个可用区来添加冗余](https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/use-fault-isolation-to-protect-your-workload.html)，也就是再部署 10 个 EC2 实例以便在可用区出现故障时处理负载，这样 EC2 实例的总数就是 20 个（遵循静态稳定模式）。

 虽然看起来有 10 个备用 EC2 实例，但实际上我们只部署了一个备用可用区，还没有超过收益递减的边界。但是，您还可以使用 3 个可用区，并在每个可用区部署 5 个 EC2 实例，这样可以进一步提高成本效益和可用性。

 这时有 1 个可用区处于备用状态，总共有 15 个 EC2 实例（而不是 2 个可用区和 20 个实例）。如果出现影响单个可用区的事件，这种配置仍然可以提供所需的 10 个实例来处理峰值容量。因此，您应该设置备件，以便在工作负载使用的所有故障隔离边界（实例、单元、可用区和区域）内建立容错能力。

# CAP 定理
<a name="cap-theorem"></a>

 可用性的另一个方面与 CAP 定理有关。该定理指出，一个由存储数据的多个节点组成的分布式系统最多只能同时实现以下三点中的两点：
+  **一致性 (C)**：如果保证不了一致性，每个读取请求都会收到最新的写入内容或错误。
+  **可用性 (A)**：即使节点已关闭或不可用，每个请求也都会收到非错误响应。
+  **分区容错性 (P)**：尽管节点之间丢失了任意数量的消息，但系统仍能继续运行。

（有关更多详细信息，请参阅 Seth Gilbert 和 Nancy Lynch 的文章：[http://dl.acm.org/citation.cfm?id=564601&CFID=609557487&CFTOKEN=15997970](http://dl.acm.org/citation.cfm?id=564601&CFID=609557487&CFTOKEN=15997970)，*ACM SIGACT News*，第 33 卷第 2 期（2002 年），第 51—59 页。） 

 大多数分布式系统都必须容许网络故障，因此就必须允许网络分区。这意味着在出现网络分区时，工作负载就必须在一致性和可用性之间做出选择。如果选择可用性，那么工作负载就会始终返回响应，但数据可能不一致。如果选择一致性，那么在网络分区期间，工作负载会返回错误，因为其无法确定数据的一致性。

 对于以提供更高级别的可用性为目标的工作负载来说，他们可能会选择可用性和分区容错性（A 和 P），以防止在网络分区期间返回错误（不可用）。这就要求使用更宽松[的一致性模型](https://en.wikipedia.org/wiki/Consistency_model)，例如最终一致性或单调一致性。

# 容错能力和故障隔离
<a name="fault-tolerance-and-fault-isolation"></a>

 这是与可用性有关的两个重要概念。容错能力是[承受子系统故障](https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/design-your-workload-to-withstand-component-failures.html)并保持可用性（满足已有 SLA 的要求）的能力。为了实现容错能力，工作负载会使用备用（或冗余）子系统。当冗余中的一个子系统出现故障时，另一个子系统通常会以几乎无缝的方式接手其工作。在这种情况下，备件是真正的备用容量，可以承担故障子系统的 100% 的工作。使用真正的备件时，需要多个子系统故障才能对工作负载产生不利影响。

 故障隔离可以尽可能缩小故障发生时的影响范围。这一结果通常通过模块化来实现。工作负载被分解为多个小型子系统，这些子系统的故障互不影响，可以单独修复。一个模块的故障[不会传播到模块之外。](https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/design-interactions-in-a-distributed-system-to-mitigate-or-withstand-failures.html)这种效果既可以在一个工作负载中的不同功能之间纵向实现，也可以在提供相同功能的多个子系统之间横向实现。这些模块充当故障容器，可以限制事件的影响范围。

 控制平面、数据平面和静态稳定性架构模式可以直接支持容错能力和故障隔离的实现。Amazon Builders Library 文章[使用可用区的静态稳定性](https://aws.amazon.com/builders-library/static-stability-using-availability-zones)针对这几个术语给出了准确的定义，并介绍了它们如何应用于具有韧性和高可用性的工作负载的构建过程。本白皮书在[设计高可用性分布式系统AWS一节中使用了这些模式，](designing-highly-available-distributed-systems-on-aws.md#designing-highly-available-distributed-systems-on-aws.title)并在这里总结了它们的定义。
+  **控制平面** — 工作负载中涉及做出更改的部分：添加资源、删除资源、修改资源以及将这些更改传播到所需位置。与数据平面相比，控制平面通常更加复杂并具有更多活动部件，因此从统计学上讲，控制平面失效的可能性更大，可用性也更低。
+  **数据平面** — 工作负载中提供日常业务功能的部分。与控制平面相比，数据平面往往更加简单，运行容量也更高，因此可用性更高。
+  **静态稳定性** — 工作负载在依赖项受损的情况下继续正常运行的能力。一种实现方式是从数据平面中移除控制平面依赖项。另一种方式是松散地耦合工作负载依赖项。工作负载可能看不到其依赖项本应提供的任何更新信息（例如新内容、删除的内容或修改过的内容）。但是，它在依赖项受损之前所做的一切仍然在发挥作用。

 当工作负载受损时，我们可以考虑两种恢复方式。第一种是在损伤发生后对其做出反应，比如用 AWS Auto Scaling 来增加新的容量。第二种是在损伤发生之前做好准备，比如超额配置工作负载的基础架构，让它不需要额外资源就可以继续正常运行。

 静态稳定的系统采用后一种方式。它预先配置了备用容量，以便在故障期间保持可用性。采用这种方式，我们就不需要在工作负载恢复路径中在控制平面上创建依赖项以便配置新容量，从而从故障中恢复。此外，为各种资源配置新容量需要耗费时间。在等待新容量时，工作负载可能会因现有需求而超负荷并进一步降级，从而降低或完全失去可用性。但是，您还应该对照可用性目标，考虑使用预配置容量所产生的成本影响。

 静态稳定性针对高可用性工作负载提出了以下两项规则。

**规则 7**  
 不要在数据平面中的控制平面上设置依赖项，尤其是在恢复期间。

**规则 8**  
 尽可能松散地耦合依赖项，让工作负载在依赖项受损时也能正常运行。