分布式系统中的工程可靠性和容错性


用户希望可以依赖提供给他们的服务。在实践中,因为个别不可避免地的因素,可能会导致服务失败,但即使如此,我们也要尽量避免服务失败。

在本文中,我们将详细讨论什么是工程可靠性和容错性,并解释Ably平台是如何设计的,以达到工程可靠性和容错性。

作为讨论的前提,首先是一些定义:

可靠性:用户对产品或服务的可信赖程度。这意味着系统不仅可用,而且还设计了大量冗余措施,以继续按照用户的期望工作。

可用性:产品或服务在需要时的可用程度。这通常归结为,在系统出现故障时,是否能够提供足够的资源冗余。

什么是容错性?

容错性是指系统的某些组件或子系统出现故障时,依然具备可用性和可靠性。

具备容错性的系统可以容忍故障,它们旨在减轻不利因素对系统的影响,并确保系统对用户保持始终可用。容错技术可用于提高可用性和可靠性。

可用性可以粗略地认为是保证系统的正常运行;可靠性可以被认为是系统在运行期间提供服务的质量——也就是说,确保在系统故障中尽可能有效地维护功能和用户体验。

如果该服务无法提供使用,那就是可用性不足。如果服务可用,但在使用时偏离了用户预期,那就是可靠性不足。容错设计方法解决了这些不足,为业务和用户体验提供了连续性。

可用性、可靠性和状态

在大多数情况下,容错设计的主要基础是:冗余。提供超过服务所需的最小组件数量或容量。关键问题是如何管理“冗余”。

在真实世界中,可用性和可靠性存在着区别:
  • 可用性可以接受服务的短暂停止,好比更换汽车轮胎;
  • 可靠性需要确保服务的连续性,那么“冗余”必不可少。比如飞机的发动机不止一个。


连续性的要求会影响冗余容量的提供方式。

在分布式系统中,我们可以将组件分为两类,分别为“有状态”和“无状态”。

无状态组件在不依赖于任何状态的情况下就能实现功能。每次服务调用都可以独立完成,不依赖于上一次服务调用。这些组件的容错设计相对简单:只需提供足够的资源,即使某些资源出现故障,也可以由其他无状态组件负责处理。

有状态组件需要依赖于某个状态才能提供服务。状态隐式地将服务的调用链接到过去和将来的调用。这些组件的容错本质,就像飞机的引擎一样,是否能够提供运行的连续性。具体来说,就是服务所依赖的状态的连续性。

在本文的剩余部分中,我们将给出每种情况的示例,并解释在实践中实现容错时遇到的工程挑战。

容错设计将故障视为常规

在大型系统中,必须假设组件故障迟早会发生,且有可能即将发生,并且预计组件故障将持续发生。

在大型系统中,故障通常是非二进制的,我们不能依赖于单个组件的可靠性,服务故障具有传导作用。拜占庭故障就是一个很经典的例子。

例如,某个组件间歇性的工作,或某个组件产生误导性输出,或者你依赖的外部组件出现故障。这都将影响你整个系统的可靠性,你的系统能否容忍这些错误。

容忍非二进制故障需要大量的思考、工程实践,有时还需要人为干预。必须对每个潜在故障进行识别和分类,然后必须能够快速补救,或通过广泛的测试和稳健的设计决策来避免。设计容错系统的核心挑战是了解故障的本质,以及如何检测和补救故障,特别是在发生间歇性故障时,尽可能地持续为用户提供服务。

无状态服务

无状态服务对单个组件的服务连续性没有要求。资源的可用性直接转换为组件的可用性。保证资源的可用性是保证无状态服务可用性的关键。只要有可能,组件都应该被设计成无状态,不仅便于提升可用性,也便于提升可伸缩性。

对于无状态服务,有多个独立可用的组件来持续提供服务就足够了。因为没有状态,单个组件的耐久性就不值得关注。
1.png

然而,仅拥有足够的资源是不够的,你还必须有效地使用它们。你必须要有一种检测资源可用性的方法,并在冗余资源之间实现负载均衡。

因此,你必须回答以下问题:
  • 如何在各种各样的失败中生存?
  • 什么级别的冗余是可能的?
  • 维持这些冗余级别的资源,性能成本是多少?
  • 管理这些冗余级别的资源,运营成本是多少?


由此产生的权衡如下:
  • 实现用户对于高可用性的需求
  • 经营成本
  • 现实中,使之成为可能的工程可行性


冗余组件以及它们的依赖关系必须以独立的方式进行设计、配置和操作。简单的数学公式是:随着冗余级别的增加,在统计学上,独立组件的故障,使整个系统发生灾难性故障的几率将呈指数级降低。

在Ably,为了提高系统的可用性,我们将组件分配到多个可用性区域,以防止单个可用性区域出现故障。对于AWS服务来说,这很容易实现。有时多个可用性区域(AZ)也会同时出现故障;有时可能存在本地连接问题,无法访问该区域;有时,某个地区可能存在容量限制,无法支持该地区的所有服务。因此,我们还通过在多个地区(region)提供服务来提高服务可用性。

建立跨多个地区的冗余并不像支持多个区域那么简单。例如,简单地用一个负载均衡器在各地区之间分配请求是没有意义的,因为负载均衡器本身可能存在于某个区域内,它也可能变得不可用。

相反,我们使用一系列措施来确保客户端请求在任何时候都可以被路由到一个被认为是健康的且具有可用服务的区域。

有状态服务

在Ably,可靠性意味着有状态服务的业务连续性,这是一个比可用性要复杂得多的问题。

有状态服务对状态有内在的依赖关系,这种依赖关系在每次单独的服务调用中都存在。该状态的连续性转化为服务的正确性。对连续性的要求意味着服务的容错性,我们可以通过冗余,以保障在出现故障时状态不会丢失。通过共识机制来解决可能的拜占庭故障。

最简单的类比是飞机安全。一架飞机坠毁是灾难性的,飞机必须提供持续的服务。如果没有这样做,状态就会丢失,飞机就会坠毁。

对于任何依赖于状态的服务,当选择一个替代服务时,要求能够在前一个服务中断的地方继续使用新服务。因此,保存状态是必须的,在这些情况下,仅可用性是不够的。

在Ably,我们为无状态服务提供足够的计算能力,以支持我们所有客户的可用性需求。然而,对于有状态服务,我们不仅需要提供冗余服务,还需要有特定的机制来利用冗余,以支持我们的服务保证功能的连续性。
2.png

例如,某个请求在集群中的某个实例上运行,而该实例恰巧遇到故障,迫使该请求转移,则必须有适当的机制来确保请求能够继续执行。

为达到继续执行的目的,这是几个层面配合作用的效果。在一个层面上,必须存在一种机制,以确保该请求被重新分配给一个健康的服务。在另一个层面上,需要确保重新分配的服务在前一个服务停止的地方继续执行。此外,每一种服务本身都是通过一定程度的冗余来实现和操作的,以保证服务的总体可靠性。

该机制的有效性直接转化为服务的有效性。以一个场景为例:对于任何待处理的消息,你需要确切地知道该消息的处理结果,成功或失败。

当客户端将消息提交给Ably以进行发布时,服务接受该消息以进行发布,客户端希望获得消息的结果。此时,主要的可用性问题是:服务接受消息或者拒绝消息的时间分别是多少?

我们最低的可接受时间是4秒。

如果你想要发布消息,而我们却告诉你我们做不到,那么这是一个可用性缺陷。这不是很好,但你至少知道当前我们的服务不可用。

然而,如果我们成功地回应,“是的,我们已经收到了你的信息”,但我们却没有真正的继续执行下去,那就是另一种失败。那这就是我们功能性的问题,是可靠性的缺陷。而且在分布式系统中要解决可靠性问题要复杂得多,需要花费大量的工程精力和复杂性来满足可靠性要求。

实现可靠性的架构方法

下面阐述了我们在Ably采用的架构方法,如何利用冗余来处理消息。

哈希一致性

通常,水平伸缩性是通过分配可伸缩的资源来实现的。就无状态服务而言,我们将服务部署在不同的地理位置,当请求来临时,负载均衡器会根据地理位置分配邻近的服务或其他优化考虑因素来决定处理请求的服务。

同时,针对有状态的服务,服务器的放置特别重要,当某一台服务器发生故障时,不能影响其他服务器的正常操作,我们可以通过哈希一致性来达到目的。

一个具体的例子是,我们通过哈希一致性算法来决定消息由哪个服务器来处理,同时尽最大可能,将消息均匀的分配给不同的服务器进行处理。
3.png

消息持久化

当消息发布后,消息被处理,返回响应(成功或失败)给调用方。可靠性意味着消息不能丢失。反过来意味着,只有将消息持久化下来,当服务器发生故障时,消息依然可以被找回,进行后续的处理。

首先,我们在至少两个不同的可用性区域(AZs)中记录消息的接收情况。这是Ably消息持久化的核心:将消息写入多个位置,并且确保写入消息的过程是事务性的。你总能知道消息要么成功写入,要么失败。有了这一点的保证,就可以保证消息的后续处理。

确保消息在多个可用性区域中被持久化,因此单个事件或原因不会导致数据丢失。确保多个位置的写操作是事务性的,则需要消息持久层中的分布式一致性。
4.png

以这种方式构建的系统,只有在所有可用性区域同时发生故障时才会导致系统不可用,但这种概率是极低的。

在我们的数学模型中,当一个节点发生故障时,且我们已经知道了修复故障所需的时间,再加上每个可用性区域的故障率,以及各个可用性区域连续发生故障的概率进行建模。最后我们得出我们需要8-9个可用性区域来最大化的保证可靠性。

容错性的工程考虑

即使你有了实现容错性的理论方法,仍然有许多实际的和系统工程方面的挑战需要考虑。

分布式一致性

上述机制,例如哈希一致性,只有在所有服务器正常工作时才有效。

这是一个经典的一致性问题,集群中的成员,对自己的身份(主从关系)达成一致性,Raft/Paxos是重要的理论保障,但在实际的网络环境中,特别是,在跨多个区域的网络中,如果各个服务器之间的网络延迟过大,这些算法的有效性就会下降。

只有当所有参与实体对集群的拓扑以及每个节点的状态和健康状况达成一致时,上述机制(如角色放置算法)才能有效。

相反,我们同时使用Gossip协议,它是最终一致的、容错的,并且可以跨区域工作。

结论

容错性的目的是减轻故障对系统的影响,以便持续地为客户提供服务。

在Ably中,我们将服务分为有状态和无状态两类。无状态服务的容错性极强,而有状态服务我们需要保证状态的连续性,才能保证服务的连续性。

要实现容错性系统,必须将故障视为常规事件,而不是异常事件。除了理论的支撑,设计容错系统还涉及许多系统工程挑战。这包括基础设施可用性和可伸缩性问题,以及分布式一致性问题,如何协调全球所有节点的网络拓扑结构,以及不可预测/难以检测的节点健康状态。

Ably平台是根据这些原则从头设计的,其目标是提供一流的企业解决方案。这就是为什么我们可以自信地提供可用性和可靠性服务的保证,同时保证容错性。

原文链接:Engineering dependability and fault tolerance in a distributed system (翻译:钟涛)

0 个评论

要回复文章请先登录注册