使用日常商业质量的硬件和软件,可以实现“五个9”的可靠性。关键是这些组件的组合方式。
我们的社会已经开始期待它使用的许多系统提供不间断的服务,例如电话、自动柜员机和信用卡验证网络。其中许多被实现为嵌入式系统。
嵌入式设计人员越来越多地被要求创建能够可靠运行的系统,达到99.999%的时间(称为“五个9”可用性),这相当于每天不到1秒的停机时间。这些系统称为高可用性系统。
高可用性系统的设计基于冗余硬件组件和软件的组合,以管理故障检测和纠正,而无需人工干预。在本文中,我们将快速回顾一些与高可用性和故障管理相关的定义,然后继续讨论容错系统的一些硬件和软件设计模式。
故障与故障(Fault vs. failure)
当我们设计高可用性系统时,我们需要将大部分设计工作集中在故障和故障上。为了更好地定义,我们可以将故障(failure )定义为系统提供的服务不符合其规范的情况。我更喜欢一个将系统提供的服务与我们的期望进行比较的定义,而不是一个规范。但我们的期望往往是主观的,没有很好的文档记录。因此,我们将坚持将系统的服务与其规范进行比较,尽管这会让我们面临来自规范错误或遗漏的问题。
另一方面,故障( fault)是交互系统的故障。这就是我们所认为的不希望发生的事情的可能原因。因此,故障可能是我们系统的子系统故障、组件故障、外部系统故障或编程错误。故障会引发更多的各种故障。或者它们会引发失败。或者,它们可以在不引发任何失败的情况下发生。
例如,故障可能是卡车司机没有遵守金门大桥的负载限制。如果卡车超过负载极限20%或30%,我们预计桥梁不会损坏。但另一个故障可能是同一辆卡车撞上了桥梁道路的中间分隔带。这也不会导致大桥发生物理故障,但可能会导致大桥在数小时内无法实现将人员和车辆穿过金门水道的特定目的。
故障可能是暂时性、永久性或间歇性的。当它们被激活时,可能会导致系统或子系统的状态出错。这些错误可能会引发系统故障。处理故障有四种主要方法:
- 故障预测(Fault forecasting)
- 故障避免 (Fault avoidance)
- 故障排除 (Fault removal)
- 容错性 (Fault tolerance)
故障预测使用数学模型和实际实验来提供故障存在及其后果的估计。例如,一种实用的故障预测技术是将故障注入系统,并研究任何由此产生的故障。
通过使用严格的系统、硬件和软件开发过程(可能包括正式规范和验证技术)来实现故障避免和排除。
容错是通过使用冗余的、可能是多样的系统实现来实现的,以避免故障的影响。实现容错的一种方法被称为“优雅降级”(或“软故障”)——如果不能提供完整的系统性能,则提供合理的部分功能。另一种方式被称为“故障安全”(或“故障停止”)-当出现故障时,将系统停止在安全状态,而不是继续运行。
容错的主要概念是冗余。它基于这样的想法(或希望),即多个独立的故障不会一起袭击您的系统。应设计容错系统以避免单点故障。换言之,如果系统的一部分可能发生故障,那么系统中应该有一个冗余部分可以弥补故障,从而避免故障。
冗余有多种形式:
- 硬件冗余(低级、高级或两者)
- 软件冗余
- 时间冗余
- 信息冗余
硬件冗余的例子包括自检逻辑电路和一架飞机上的多个飞行计算机。软件冗余可能使用两种完全不同的算法来计算相同的结果。时间冗余可以通过通信重新传输来实现。信息冗余可以使用备份、校验和和纠错码来实现。
冗余可以是动态的或静态的。两者都使用复制的系统元素。在静态冗余中,所有副本同时处于活动状态。如果一个复制副本“抛出错误”,则可以立即使用其他复制副本以允许系统继续正确操作。在动态冗余中,一个副本是活动的,其他副本是被动的。如果主动复制副本引发故障,则先前被动复制副本将被激活并接管关键操作。
那么,所有这些与实现高可用性有什么关系呢?首先,一个定义。高可用性是指系统能够容忍故障并根据其规范继续提供服务的能力。系统可以使用此处描述的所有概念和方法来实现高可用性。
可用性通常以“可用性百分比”或“每年停机时间”为单位来衡量。典型的容错系统可能在99.99%的时间内可用,或每年停机不到一小时(每天10秒)。但高可用性系统预计在99.999%的时间内可用,或者每年可用时间不到五分钟(大约每天一秒)。这通常意味着,当出现故障时,必须自动处理。人类太慢,无法在所需的时间内消除或掩盖任何问题。
硬件冗余
与其将硬件构建为由超级可靠组件构建的单个超级可靠模块,不如使用由日常商业质量组件构建的日常商业质量硬件的冗余复制模块通常更具成本效益。
每个复制副本通常都设计为表现出“快速故障”或“故障停止”行为。这大大简化了故障管理决策:每一次故障都会使硬件停止运行;而不是试图一瘸一拐地向故障管理器挑战,以找出模块的哪些输出现在有故障,哪些输出仍然良好。
对于使用静态冗余的容错,每个复制模块可以具有日常商业可靠性。使用两个副本称为配对或双工。如果使用N个副本,则称为N丛。
图1:三重冗余硬件
图1显示了一种3倍或三倍冗余的硬件设计。三个副本显示在图表底部附近。它们将它们的输出提供给“投票器”,后者决定子系统的实际最终输出。当N≥3时,“投票人”通常使用多数决策。但是,这需要是大多数未失败的副本,而不仅仅是副本总数(失败和未失败)的简单多数。
但是,选民不只是一个硬件和软件,可能会像我们系统中的任何其他模块一样出现故障吗?事实上,它可以;如果这样做的话,会给我们的系统带来灾难。但选民通常是非常简单的单位,可以设计和测试以确保其稳健性。或者,您可以创建涉及复制选民和选民的二级选民等的设计。但我们这里不讨论这一点。
对于使用动态冗余的容错,复制的模块仍然具有日常商业可靠性。一种方法是使用由一个活动模块和一个备用模块组成的冗余对。另一种方法是使用模块集群。这些模块不必彼此精确复制,可以具有不同的特性、接口和容量。集群需要故障切换策略来决定当主模块引发故障时如何管理多个模块。以下是一些选择:
- 备用备份(Standby backup)。当主模块在系统中运行时,一个备份模块处于“待机”状态,监视主模块是否存在故障,并准备启动并接管。例如,可以使用这种方法设计高可用性web服务器。
- 旋转备用(Rotating standby)。当主模块在系统中运行时,可能有多个备份模块。主系统出现故障时,一个备份将接管系统的运行。航天飞机上的飞行计算机是按照这样的理念设计的:主模块由一对计算机组成,它们必须始终彼此一致。第一个备份模块是一对类似的模块。但航天飞机上的第二个备用模块是一台计算机,只能通过人工指挥才能接管。
- 故障切换到非关键模块(Failover to non-critical module)。主模块运行系统的关键资源。备份模块可以运行其他非关键功能,但在发生故障时,它可以接管主模块的最关键服务。作为人类,当我们试图发送紧急电子邮件时,电脑的高速互联网连接出现故障,我们会迅速切换到我们从未想过会再次需要的旧调制解调器。
- 相互接管(Mutual takeover)。每个模块运行自己的关键资源,但在发生故障时可以接管另一个模块的关键资源。例如,在心脏重症监护病房中,每8名患者应有一台心脏监测计算机。但是,如果相邻的心脏监测计算机出现故障,每台计算机可以处理另外八名患者(可能有一些轻微的退化)。
故障切换的正确实施至关重要。如果引发故障的主模块继续运行,同时另一个模块试图接管其服务,这将是一场灾难。他们的服务可能会以意想不到的方式发生冲突。如果一个主模块在抛出故障后被停止,而没有其他模块来接管它的服务,这可能同样是灾难性的。因此,故障切换的验证和测试是至关重要的,尽管这不是我们很多人喜欢做的事情。
软件冗余
大多数硬件故障都是随机的,是由物理缺陷造成的,这些缺陷要么在制造过程中持续存在,要么随着组件磨损或受到周围物理世界的冲击而发展。另一方面,软件故障不是物理故障;软件不会磨损。相反,软件故障是由于调用包含软件设计或实现中始终存在的缺陷的软件路径而导致的。由于软件通常比硬件更复杂,因此可以预期它具有更多的内置缺陷,从而导致比硬件故障更多的软件故障。软件容错设计成本也比硬件容错高。
N版本编程是一种成熟的软件容错设计模式。当我在70年代第一次遇到它时,它被称为不同的软件。它是硬件N复用的软件等价物(见图1)。但这并不像硬件N丛的复制那样简单,即同一软件的N个副本将包含相同的错误,并产生N次相同的错误。在N版本编程中,如果某些软件功能的N个单元需要并行运行,它们需要是该功能的N种不同实现,由N个独立的开发团队独立实现。这是N版编程。
1996年6月,阿丽亚娜-5号卫星发射火箭的第一次飞行在到达4000米的高度时发生了火球爆炸。尽管存在硬件冗余,但火箭惯性参考系统(其数字飞行控制的一部分)的故障导致了故障,因为软件冗余没有得到妥善处理。阿丽亚娜-5号上有两台惯性参考计算机,一台处于活动状态,另一台处于“热”备用状态。两者都并行运行,硬件和软件完全相同。该软件与阿丽亚娜-4号上的软件几乎相同,阿丽亚娜4号是一款较旧且成功的运载火箭。但阿丽亚娜-5号上的一些飞行参数值大于阿丽亚娜-4号,因此数据值溢出。该错误是通过关闭计算机来处理的。由于冗余计算机运行的是相同的软件,它也受到了数据溢出的影响,而且很快就被关闭了,整个惯性参考系统都死了。结果,发动机的喷嘴旋转到极端位置,导致火箭突然转向并在自毁前破裂。[1] 数据溢出错误的处理方式适用于随机发生的硬件错误,但不适用于两台计算机上发生的类似软件错误。两台计算机上类似的软件错误可以通过N版本编程避免。
回到70年代,我们认为N版本编程是软件容错的最先进技术。从那时起,这种设计模式出现了许多问题:当你使用它时,软件开发成本飙升,因为你需要支付N个独立的小组来实现N个独立软件设计。但是,如果你试图节省一些成本,你会遇到所谓的“平均智商”问题:成本较低的开发团队拥有资质较差的软件工程师,他们会产生质量较低的代码。因此,你可能会得到N个不同的程序,这些程序都充满了错误,以N种不同的方式创建。
N版本编程的另一个失败是向N个独立开发团队提供什么作为输入的问题。一般来说,一个规范被影印并提供给所有N个开发团队。但是如果这个规范有缺陷,你会得到N个独立开发的类似缺陷软件版本,它们都会做错事。如果在系统发布后发现了规范或使用错误,那么在N个不同的实现中,每个新错误必须被修复N次,从而使维护成本过高。如今,人们通常认为,让一个顶级软件开发团队使用最好的可用基础设施、软件开发工具、技术和测试开发一个高质量的软件版本,可以更好地利用N版本编程的价格。
检查点
与N版本编程的静态冗余不同,许多软件容错设计模式基于动态冗余。它们都采取四个阶段的方法:
- 错误检测
- 损害评估和限制(有时称为“防火墙”)
- 错误恢复
- 故障处理和持续服务
在这些阶段中的第二阶段,当检测到软件错误时,通常采用故障停止方法。但软件是高度复杂的,因此,如何消除围绕错误的错误软件行为的影响通常是不清楚的。在这方面,一个有用的工具是交易的概念。事务是对应用程序状态的操作的集合,因此事务的开始和结束是应用程序处于一致状态的点。例如,每个城市的市政厅都有一个文件系统,其中包含该城镇所有居民的信息。当两个人结婚时,他们的名字和结婚日期都记录在一个名为“已婚夫妇”的文件中。此外,新郎的身份在名为“男性居民”的文件中将从单身变为已婚。新娘的身份在称为“女性居民”的文件中将从单身变成已婚。“如果三个文件更新中的一个失败,或者软件在一路上崩溃,我们需要回到婚姻”交易的开始。“否则,我们可能会出现不一致的状态,比如新郎被列为已婚,而他的妻子仍然被列为单身。”。只有在婚姻交易开始时,以及婚姻交易成功结束时,情况才是一致的。
如果我们想使用事务的概念来容错,我们的系统必须能够在事务开始时保存其状态。这叫做检查点。它包括在即将开始新事务的第一步时对软件状态进行“快照”。仅当上一个事务在无错误状态下完成时,才会拍摄快照。这里的基本恢复策略是重新执行:当在事务期间检测到错误时,事务将失败停止,系统将重新加载回上次保存的检查点。然后从该检查点继续服务,允许新事务建立在其一致状态上。但是,失败停止的事务将丢失。这种错误恢复被称为向后错误恢复,因为软件状态被回滚到过去的无错误点。
图2:检查点回滚场景
简单的检查点有自己危险的单点故障。在拍摄检查点本身的快照过程中可能会出现故障。但有一种解决方案,有时称为检查点回滚。如图2所示。在该图中,省略号表示通过队列发送消息来彼此通信的软件客户端和软件服务器。单个事务可能包括从客户端到服务器的许多请求消息,以及从服务器到客户端的许多响应消息。在事务处理期间,数据在服务器内被修改。在事务结束时,应在右侧显示的两个永久性大容量存储设备中的每一个上记录一组一致的数据。应连同数据一起记录交易序列号。如果稍后检测到错误并且服务器已停止,则可以重新启动此服务器(或启动副本服务器)。作为启动恢复过程的一部分,将检查两个大容量存储设备上的事务序列号。服务器数据将从包含最高序列号的设备中恢复。(另一个大容量存储设备可能包含较低的序列号,因为故障发生在该设备的检查点期间。)
进程对
这种检查点设计模式的一个限制是故障后的恢复可能需要很长时间。启动或重新启动服务器可能需要大量处理,也可能需要将数据恢复回检查点。“热备份”服务器直接使用其自身的永久性大容量存储设备,可以加快恢复速度。这种设计模式称为过程对;如图3所示。
图3:流程对
在这里,我们看到位于图中心的主服务器的工作情况与前面的检查点场景中的工作情况非常相似。客户端直接使用此主服务器。每当主服务器成功完成整个事务时,它就会将有关其新的一致状态的信息传递给备份服务器(右侧)。主服务器和备份服务器都将数据记录在其永久性大容量存储设备中。通过这种方式,备份服务器可以随时了解已完成的事务。当主服务器启动并可供客户端使用时,它会定期向备份服务器发送心跳消息。这些心跳消息可以与一些检查点消息组合。如果备份服务器检测到心跳消息流已停止,则它知道主服务器已停止或不可用,并且它将很快接管新的主服务器。
当客户端、主服务器和备份服务器都在不同的处理器或模块上时,以及当它们都在一个处理器上时,这种设计模式可以工作。
尽管过程对设计模式非常复杂,但仍存在一些问题。例如,检查点消息可能会丢失或无序到达备份服务器。或者,如果主服务器是物理设备的控制器,并且在操作过程中发生故障,则可能会出现问题。备份服务器在接管时可能不知道如何完成操作,因为它不知道主服务器在发生故障之前走了多远。(例如:当发生故障时,主服务器已将核反应堆的石墨棒移动了所需的30cm。)
同样,在流程对设计模式中,当主服务器发生故障时正在进行的事务很可能在执行过程中丢失或丢弃。备份服务器将以主服务器在失败之前向其报告的上次完成事务的状态进行接管。
恢复块
动态软件冗余的另一种设计模式称为恢复块。它基于检查点,但它增加了对软件处理结果的验收测试。您需要像N版本编程一样准备许多处理数据的替代方法,但您并没有为每个事务运行所有处理替代方法。相反,您选择一种处理数据的方法作为主要替代方法,并首先尝试仅使用主要替代方法的处理来执行事务。在主处理结束时,运行验收测试。如果通过了验收测试,你就完成了。如果验收测试失败,则继续尝试下一个替代处理,如图4所示。
图4:恢复块
如图所示,在首次进入恢复块之前,必须执行检查点操作。每次验收测试失败后,软件必须回滚到检查点状态。验收测试是在尝试的每个处理备选方案之后进行的。以这种方式,在处理备选方案成功交付通过验收测试的结果之前,将运行处理备选方案。这些“良好”输出构成了恢复块的最终结果。
前向错误恢复
检查点回滚、进程对和恢复块都是执行向后错误恢复的方法。大多数动态软件容错是使用反向错误恢复来完成的,但正向错误恢复是另一种选择。它的主要思想是从错误状态继续,并进行纠正,以清理受损状态。前向错误恢复取决于准确的损伤评估,通常是系统特定的。前向错误恢复的一个例子是异常处理,它在检测到问题时将控制权传递给专门的异常处理软件,而不是回滚并从以前的检查点状态继续。
前向错误恢复的一种设计模式称为替代处理。当一个事务有两个(或多个)处理备选方案时,可以使用此方法,其中一个备选方案通常非常精确但计算复杂,另一个备选更简单且性能更高。如果主处理出现故障,则第二个备选方案可以用作测试和辅助结果提供程序。例如,平方根算法可以是主要处理,表查找插值可以是其替代方法。一旦算法运行完毕,可以使用表查找插值来测试平方根算法结果的质量,并快速修复这些结果中的一些错误。
图5:飞机飞行控制的替代处理
在图5中,我们看到两个数字飞行控制系统同时运行,以控制一架飞机(波音777)。[2] 图右侧的决策逻辑使用更简单的飞行控制系统的输出作为测试复杂的主飞行控制系统输出的标尺。如果验收测试失败,那么更简单的飞行控制系统的输出也可以作为失败的主要输出的替代。(向左的箭头表示替代处理的结果也可用于向主处理提供反馈。)
这样的设计模式允许将日常商业质量的硬件和软件用作真正高可用性系统的构建块,这些系统可以在无需人工干预的情况下实现“五个9”或更高的可用性。
David Kalinsky是OSE Systems的客户教育总监。他是嵌入式软件技术的讲师和研讨会领导者。近年来,David建立了软件工程方面的高科技培训项目,以开发实时和嵌入式系统。在此之前,他参与了许多嵌入式医疗和航空航天系统的设计。大卫拥有耶鲁大学核物理学博士学位。可以在联系到他。