可伸缩架构(第2版):云环境下的高可用与风险管理
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

两次失误的高度

如果你曾经操作过无线电控制(R/C)飞机,可能听说过那句话“让你的飞机保持两次失误的高度”。当你学习如何操纵R/C飞机,尤其是开始学习如何进行特技飞行时,你会学得很快。失误其实就相当于高度,出现一个失误,你就失去一定的高度。当你失去太多高度时,坏事就发生了。因此,让你的飞机保持“两次失误的高度”意味着让你的飞机飞得足够高,从而有足够的高度从两个不相关的失误中恢复飞行。

你可以想象一下:在恢复飞行的过程中,你通常压力很大,而且可能处于一种惊恐的情绪中,很可能会做出一些反常的事情—正是这种情形让你可能产生另一个失误。因此如果你飞得不够高,就会坠机。

换一种角度说,如果你能够在两次失误的高度飞行,即使发生了一次失误,也总有一次能够从失误中恢复的机会。

同样的思想,对于我们创建高可用、大规模的应用程序是非常重要的。

我们如何在应用程序中“保持两次失误的高度”?对于初学者而言,当确定出系统要面对的失败场景后,我们遍历所有可能存在的分支场景,并制订相应的恢复计划。需要确定恢复计划本身没有错误或者缺陷—简而言之,需要检查恢复计划是否可以正常工作。如果发现它不能正常工作,那么就应当重新制订恢复计划。

这只是一个可能会用到“两次失误的高度”的场景,当然还有很多其他的场景,我们会通过一些示例来说明它们对应用程序的影响。

场景1:丢失一个节点

我们来看一个跟Web服务流量有关的示例场景。

假设你正在开发一个预计每秒处理1000个请求(请求/秒)的服务,而且我们假设服务中的单个节点每秒只能处理300个请求。

问题:你需要多少个节点才能支撑该流量?

通过一些简单的数学计算我们可以得到结果:

所需的节点数=请求总数/每个节点能处理的请求数量

其中:

所需的节点数

表示处理指定数量请求所需的节点数量。

请求总数

该服务预期处理的请求总数。

每个节点能处理的请求数量

服务中每个节点能处理的请求数量的平均值。

代入我们的数字之后:

所需节点数量=1000/300≈3.3

所需节点数量=4个节点

你需要4个节点来处理每秒1000个请求的服务压力。转换一下,当使用4个节点时,每个节点需要处理:

每个节点处理的请求数量=请求总数/节点数量

每个节点处理的请求数量=1000/4=250

每个节点需要每秒处理250个请求,这低于每秒300个请求的单个节点限制,如图2-1所示。

图2-1:4个节点,每个节点每秒处理250个请求

现在你有了4个节点。你不仅可以处理预计的流量,并且因为有了4个节点,可以允许丢失掉1个节点。你已经做好丢失1个节点的准备了,是不是?真的吗?

当然,实际上你并没有。如果你在流量峰值时丢失1个节点,你的服务仍然会失败。为什么?因为如果丢失1个节点,你的流量会分布在剩余3个节点上。此时:

每个节点处理的请求数量=请求总数/节点数量

每个节点处理的请求数量=1000/3=333

每个节点需要每秒处理333个请求,这超过了每秒300个请求的节点限制,如图2-2所示。

图2-2:4个节点,一旦某个节点出现故障就会影响到其余3个节点

因为每个节点每秒只能处理300个请求,所以服务器现在已经过载。在当前情况下,或者给所有用户提供较差的性能,或者会丢掉某些请求,或者以其他形式无法提供服务。总之,你的可用性开始下降了。

如你在图2-2中所见,如果系统失去了其中的1个节点,就无法继续提供完整的能力。因此,即使你以为能够从1个节点故障中恢复,但实际上却不能。你的系统现在是脆弱的。

为了能够处理节点故障,你需要4个以上的节点。如果你希望能够处理1个节点故障,那么就需要5个节点。这样,即使5个节点中的其中1个出现故障,还剩下4个节点可以处理流量:

每个节点处理的请求数量=请求总数/节点数量

每个节点处理的请求数量=1000/4=250

如图2-3所示,因为这个值低于每个节点每秒300个请求的限制,所以它们有足够的能力来继续处理流量,即使是1个节点出现了故障,也不会对系统造成任何影响。

图2-3:5个节点,1个节点出现故障不会造成其他影响

场景2:升级过程中出现的问题

另一个“两次失误的高度”的例子是升级中的应用程序。升级和日常维护可能会导致没有预期到的可用性问题。

假设你有一个平均流量为每秒1000个请求的服务。此外,我们假定服务中单个节点的处理上限是每秒300个请求。如之前的示例所述,最少需要4个节点来运行服务。为了能够处理预期流量并支持单节点故障,你为服务配置了5个节点。

现在,假设你希望对正在运行的服务进行一次软件升级。为了在升级过程中保证服务正常运行,你决定使用滚动部署的方式。

简而言之,滚动部署就是每次只升级1个节点(临时将它设置为离线状态来进行升级)。在成功升级第一个节点并重新处理流量之后,继续升级第二个节点(临时将它设置为离线)。持续这个过程直到5个节点都完成升级。

因为在每次升级时,只有1个节点是离线状态,所以总是有4个节点在处理流量。因为4个节点已经足够处理所有的流量,所以升级过程中服务不会受到影响。

这是一个很不错的计划。你已经构建了一个不仅能处理单节点失败,还可以通过滚动部署实现不停机升级的系统。

如果在升级过程中某个节点发生了故障呢?这个时候,你有1个节点因为升级不可用,还有1个节点出现故障,这样只剩下3个节点,不足以处理所有的流量。这时,你就会遇到服务降级或者系统整体不可用的情况。

在升级时遇到某个节点故障的可能性有多大呢?

你遇到过多少次升级失败呢?事实上,升级过程中一个参数带来的节点故障概率可能比其他时候大得多。升级和节点故障并不是完全孤立的。

我们得到的教训是:即使你认为有冗余节点来处理各种故障情况,但是如果有两个或多个问题同时发生(因为问题都是有关联性的),就可能会根本没有任何冗余。这样的系统就容易出现可用性的问题。

因此,要想使用每秒300个请求的节点来处理每秒1000个请求的流量,我们需要:

4个节点

可以处理流量,但是不能处理单个节点故障。

5个节点

可以处理单个节点故障,或者在维护或升级时允许单个节点不可用。

6个节点

可以处理多个节点故障,或者在维护或升级时,允许同时存在1个节点升级失败和1个节点不可用。

场景3:数据中心恢复

我们将这个问题扩大一点,来看一看数据中心的冗余和恢复。

假设你的服务正在处理每秒10,000个请求的流量。因为单个节点每秒只能处理300个请求,所以这意味着你需要34个节点,这还不考虑故障冗余和升级的情况。

为了让系统增加一些额外的处理能力,我们使用了总共40个节点(每个节点每秒处理250个请求)。现在即使失去多达6个节点也可以处理所有的流量。

让我们来做得更好一点:现在我们把这40个节点平均分布到4个数据中心,这样可以有更好的冗余性。

现在,我们可以像恢复节点故障一样来恢复数据中心故障了,如图2-4所示。但是现在就算是恢复了吗?

图2-4:4个数据中心,40个节点,有足够的能力来处理请求

答案是否定的。显然,我们可以处理单个节点故障,因为我们已经提供了额外的6个(40-34)节点。但是如果某个数据中心出现故障了怎么办?

如果某个数据中心出现故障,我们就丢失了四分之一的服务器。在这个例子中,我们就从40个节点减少到了30个节点。此时每个节点不再每秒只需处理250个请求了,而是需要每秒处理333个请求,如图2-5所示。因为这超过了单个节点的处理能力,所以我们又遇到了可用性的问题。

图2-5:4个数据中心,1个出现故障,只剩30个节点,处理请求的能力不足

即使我们使用多个数据中心,但是一旦某个数据中心出现故障,就会让我们无法再处理增长的流量。我们认为自己可以从某个数据中心的故障中恢复,但实际上不能。

因此,你究竟需要多少台服务器

究竟需要多少台服务器才能处理数据中心出现故障的情况呢?我们一起来解答这个问题。

依然基于相同的假设,即我们需要最少34台服务器才能处理所有流量。如果我们使用4个数据中心,究竟需要多少台服务器才能真正满足数据中心冗余呢?

显然,即使4个数据中心其中之一出现故障,我们需要确保任何时候都拥有34台正常工作的服务器。这意味着我们需要有34台服务器遍布在其他3个数据中心中:

每个数据中心的节点数=服务器最小数量/(数据中心数量-1)

每个数据中心的节点数=34/(4-1)

每个数据中心的节点数≈11.333≈12台服务器/数据中心

因为我们需要在每个数据中心有12台服务器,并且即使4个数据中心之一出现故障,我们在每个数据中心仍然有12台服务器,所以:

总节点数=每个数据中心的节点数×4=48

因此,我们需要48个节点来保证,即使有1个数据中心出现了故障,依然有34个节点能够工作。

如果数据中心的数量发生了变化,会对我们的计算造成什么影响呢?如果我们只有2个数据中心呢?如之前一样:

每个数据中心的节点数=服务器最小数量/(数据中心数量-1)

每个数据中心的节点数=34/(2-1)

每个数据中心的节点数=34

总节点数=每个数据中心的节点数×2=68

如果有2个数据中心,那么需要68个节点。如果拥有4个数据中心,那么需要48个节点来保证数据中心冗余。如果拥有6个数据中心,那么需要42个节点来保证数据中心冗余。

注意,随着数据中心数量的增加,所需节点的数量也在减少。这表明了一个看似奇怪的结论:

为了提供整个数据中心故障恢复的能力,当你拥有的数据中心越多时,每个数据中心需要的节点越少。

这似乎是一种倒退,与我们的直觉相差甚远。从这个例子中,我们得到的教训是:虽然其中的内容可能无法直接应用到实际情况中,但是理论依然适用。当你在设计恢复计划时一定要小心,你的直觉可能会与实际情况相背离,如果你的直觉是错误的,那么就会带来可用性的问题。如果你在前面的示例中仅凭直觉,那么会认为在任何情况下都不会有足够的节点来处理数据中心故障,或者最终节点的数量会超出所需的数量,从而无法获得所需的弹性级别。

场景4:隐蔽的共享故障类型

有些时候,很多问题看上去都是独立的,不太可能同时发生,但事实上它们却是互相关联的。这意味着在某些场景下,多个故障可能会一起出现。

假设你的服务运行在4个节点上。你为了做好充分准备,使用了一共6个节点—足够同时处理单节点故障和升级失败。

你现在放心地认为系统是安全的。

随后发生了一件事:在数据中心的机房中,某个机架上的供电系统出现了问题,导致整个机架无法工作。

通常在这个时候,你会意识到所有6个服务器都放置在同一个机架上。你是如何发现的呢?因为所有6台服务器都下线了,同时你的服务也完全无法提供了。

所以也别提什么冗余了……

当你认为自己的系统是安全的时候,可能实际上并不是这样的。我们知道不是所有问题都是完全独立的。在你的所有服务器之间,普遍存在着可能看不到或者至少是没发现的情况,那就是它们都共享着相同的机架和供电系统。

请确保仔细检查了隐藏的共享故障因素,它们可能会导致你精心准备的计划付之东流,为系统带来可用性风险。

场景5:故障循环

故障循环指的是,当某个特定问题导致系统故障时,为了修复它,你需要或者不得不制造另一个更严重的问题。

解释故障循环最好的方式是用下面这个跟服务器无关的例子。假设你生活在一个非常棒的公寓中,有一个封闭的车库来存放东西。哇!这真不错。但是这里经常停电,于是你决定买一台发电机,以备不时之需。你买了发电机、汽油,然后把它们放在车库中。生活看上去很美好。

然后,当停电时,你准备去车库找你的发电机。这时候你第一次意识到,唯一进入车库的方式就是通过车库的电子门—但是因为停电它无法打开。

哎呀!

这正是因为虽然你制订了备选计划,但并不意味着在需要时能执行它。

同样的问题也会出现在我们的服务上。一次服务故障会不会因为导致其他看起来无关的问题,从而让我们难以修复?例如,你的服务出现故障,部署一个新的版本有多简单?如果你用于部署的服务失败会怎样?如果你用来监控其他服务性能的服务失败了会怎样?

请确保你的恢复计划在问题发生时能够实现。如果不能考虑问题间的依赖关系以及相应的解决方案,你就会面临可用性的问题。