当前位置: 首页 > 科技观察

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

时间:2023-03-19 20:19:00 科技观察

用户希望能够依赖提供给他们的服务。在实践中,由于一些无法避免的因素,服务可能会失败,但即便如此,我们也应该尽量避免服务失败。在本文中,我们将详细讨论什么是工程可靠性和容错性,并解释Ably平台是如何设计来实现工程可靠性和容错性的。作为讨论的先决条件,首先定义一些:Reliability用户对产品或服务的信任程度。这意味着该系统不仅可用,而且还设计有大量冗余以继续按用户期望的方式工作。可用性产品或服务在需要时可用的程度。这通常归结为在系统发生故障时提供足够的资源冗余。什么是容错?容错性是指系统的某些组件或子系统在发生故障时的可用性和可靠性。容错系统容忍故障,它们旨在减轻对系统的不利影响,并确保系统始终对用户可用。容错技术可用于提高可用性和可靠性。可用性可以粗略地认为是保持系统正常运行;可靠性可以被认为是系统在运行期间提供的服务质量——也就是说,确保在系统出现故障时尽可能有效地维护功能和用户体验。如果服务不能使用,则可用性不足。如果服务可用,但以用户不期望的方式使用时,它就不够可靠。容错设计方法解决了这些缺陷,为业务和用户体验提供了连续性。可用性、可靠性和状态在大多数情况下,容错设计的主要依据是:冗余。提供超过服务所需的最少组件数或容量。关键问题是如何管理“冗余”。在现实世界中,可用性和可靠性是有区别的:可用性可以接受服务的短暂停止,就像换汽车轮胎一样;可靠性需要保证服务的连续性,所以“冗余”必不可少。例如,一架飞机有多个引擎。连续性要求会影响冗余容量的提供方式。在分布式系统中,我们可以将组件分为两类,即“有状态”和“无状态”。无状态组件的功能不依赖于任何状态。每个服务调用都可以独立完成,独立于之前的服务调用。这些组件的容错设计比较简单:只要提供足够的资源,即使某些资源出现故障,其他无状态组件也可以照顾它们。有状态组件需要依赖于某些状态才能提供服务。状态将服务调用隐式链接到过去和未来的调用。这些组件(例如飞机的发动机)的容错特性提供了操作的连续性。具体来说,服务所依赖的状态的连续性。在本文的其余部分,我们将举例说明每种情况,并解释在实践中实现容错所遇到的工程挑战。容错设计将故障视为例行公事在大型系统中,必须假设组件故障迟早会发生,很可能会立即发生,并且预计会继续发生。在大型系统中,故障通常是非二进制的,我们不能依赖单个组件的可靠性,服务故障是传导性的。拜占庭故障就是一个典型的例子。例如,某个组件间歇性工作,或者某个组件产生误导性输出,或者您依赖的外部组件出现故障。这会影响你整个系统的可靠性,以及你的系统能否容忍这些错误。容忍非二进制故障需要大量的思考、工程实践,有时还需要人为干预。每个潜在的故障都必须被识别和分类,然后必须能够通过广泛的测试和稳健的设计决策快速修复或避免。设计容错系统的一个核心挑战是了解故障的性质以及如何检测和修复它们,特别是在间歇性故障的情况下,以尽可能地继续为用户服务。无状态服务无状态服务对单个组件的服务连续性没有任何要求。资源的可用性直接转化为组件的可用性。保证资源的可用性是保证无状态服务可用性的关键。只要有可能,组件就应该设计成无状态的,这不仅是为了可用性,也是为了可扩展性。对于无状态服务,有多个独立可用的组件持续提供服务就足够了。因为没有状态,所以单个组件的耐用性不是问题。但是,拥有足够的资源是不够的,你还必须有效地使用它们。您必须有一种方法来检测资源可用性和冗余资源之间的负载平衡。因此,你必须回答以下问题:你如何在各种失败中幸存下来?什么级别的冗余是可能的?在这些冗余级别上维护资源的性能成本是多少?成本是多少?由此产生的权衡如下:实现用户对高可用性的要求运营成本在现实世界中实现这一点的工程可行性必须设计、配置和运行冗余组件及其依赖项。简单的数学是这样的:随着冗余级别的增加,统计上,单个组件的故障会以指数方式降低整个系统发生灾难性故障的可能性。在Ably,为了提高系统的可用性,我们将组件分布在多个可用区,以防止单个可用区发生故障。对于AWS服务,这很容易实现。有时多个可用区(AZ)同时宕机;有时可能会出现本地连接问题,无法访问该区域;有时某个地区可能存在容量限制,无法支持该地区的所有服务。因此,我们还通过在多个区域提供服务来提高服务可用性。建立跨多个区域的冗余并不像支持多个区域那么简单。例如,简单地使用负载均衡器跨区域分发请求是没有意义的,因为负载均衡器本身可能存在于一个区域内,也可能变得不可用。相反,我们使用一系列措施来确保将客户端请求路由到一个被认为健康且随时提供服务的区域。有状态服务在Ably,可靠性意味着有状态服务的业务连续性,这是一个比可用性复杂得多的问题。有状态服务对存在于每个单独服务调用中的状态具有内在依赖性。这种状态的连续性转化为服务的正确性。连续性的要求意味着服务的容错性,我们可以通过冗余来保证在出现故障时状态不会丢失。通过共识机制解决可能的拜占庭故障。最简单的类比是飞机安全。飞机失事是灾难性的,飞机必须提供持续的服务。如果不这样做,状态就会丢失,飞机就会坠毁。对于任何与州依赖的服务,选择替代服务时,必须能够继续使用上一项剩下的新服务。因此,保留状态是必须的,在这些情况下仅靠可用性是不够的。在Ably,我们提供具有足够计算能力的无状态服务来支持我们所有客户的可用性需求。但是对于有状态的服务,我们不仅需要提供冗余的服务,还需要有特定的机制来利用冗余,以支持我们的服务保障功能的连续性。例如,如果一个请求正在集群中的一个实例上运行,而该实例恰好遇到故障迫使请求被转移,则必须有适当的机制来确保请求可以继续。为了达到持续执行的目的,这就是几个层次的协同作用的结果。在一个层面上,必须存在一种机制来确保将请求重新分配给健康的服务。在另一个层面上,需要确保重新分配的服务在先前服务停止的地方继续。此外,每个服务本身都以一定程度的冗余来实现和运行,以保证服务的整体可靠性。机制的有效性直接转化为服务的有效性。举一个场景为例:对于任何一个待处理的消息,你都需要确切地知道处理该消息的结果,成功还是失败。当客户端向Ably提交要发布的消息时,服务会接受要发布的消息,并且客户端期望消息的结果。此时,主要的可用性问题是:服务接受或拒绝消息需要多长时间?我们可接受的最短时间是4秒。如果你想发布一些东西而我们告诉你我们不能,那就是可用性错误。这不是很好,但你至少知道我们的服务目前不可用。但是,如果我们成功回复“是的,我们已经收到您的消息”,但我们实际上并没有跟进,那就是另一种失败。那么这就是我们的功能性问题,是可靠性方面的缺陷。而且,在分布式系统中解决可靠性问题要复杂得多,要满足可靠性要求需要大量的工程努力和复杂性。可靠性的架构方法下面描述了我们在Ably中采用的架构方法来处理冗余消息。哈希一致性通常,水平可伸缩性是通过分配可伸缩资源来实现的。就无状态服务而言,我们将服务部署在不同的地理位置。当请求到来时,负载均衡器会根据地理位置或其他优化考虑来分配相邻的服务,以确定处理该请求的服务。同时,对于有状态的服务,服务器的放置尤为重要。当一台服务器发生故障时,不会影响其他服务器的正常运行。我们可以通过哈希一致性来达到目的。一个具体的例子就是我们使用哈希一致性算法来决定消息在哪个服务器上进行处理,同时尽量将消息平均分配到不同的服务器上进行处理。消息持久化发布消息时,将处理消息并将响应(成功或失败)返回给调用者。可靠性意味着消息不会丢失。反过来也就是只有持久化消息,当服务器出现故障时,仍然可以取回消息进行后续处理。首先,我们在至少两个不同的可用区(AZ)中记录消息的接收情况。这是Ably消息持久化的核心:将消息写入多个位置,并确保写入消息的过程是事务性的。您始终知道消息是成功写入还是失败。有了这个保证,就可以保证消息的后续处理。确保消息在多个可用性区域中持久存在,因此任何单一事件或原因都不会导致数据丢失。确保对多个位置的写入是事务性的,需要消息持久层中的分布式一致性。以这种方式构建的系统只有在所有可用区同时发生故障时才会变得不可用,但这种情况发生的可能性极低。在我们的数学模型中,当一个节点发生故障时,我们已经知道修复故障所需要的时间,加上每个可用区的故障率,以及每个可用区连续发生故障的概率来建模。最后我们得出的结论是,我们需要8-9个可用区来最大限度地保证可靠性。容错的工程考虑即使您有了实现容错的理论方法,仍然需要考虑许多实际和系统工程挑战。分布式一致性上述机制,如哈希一致性,只有在所有服务器都正常工作的情况下才能发挥作用。这是一个经典的一致性问题。集群中的成员就其身份达成共识(主从关系)。Raft/Paxos是重要的理论保证,但在实际网络环境中,尤其是在多区域网络中,如果服务器之间的网络延迟过大,这些算法的有效性就会下降。上述机制(例如角色放置算法)只有在所有参与实体就集群的拓扑结构以及每个节点的状态和健康状况达成一致的情况下才能有效。相反,我们还使用Gossip协议,该协议具有最终一致性、容错性并且可以跨区域工作。结论容错的目的是减轻故障对系统的影响,以便继续为客户提供服务。在Ably中,我们将服务分为有状态和无状态。无状态服务具有极强的容错能力,而有状态服务我们需要保证状态的连续性,才能保证服务的连续性。协调要实现容错系统,必须将故障视为例行事件,而不是例外事件。设计容错系统涉及理论基础之外的许多系统工程挑战。这包括基础设施可用性和可扩展性问题,以及分布式一致性问题,如何协调全球所有节点的网络拓扑,以及不可预测/难以检测的节点健康状态。Ably平台是根据这些原则从头开始设计的,目标是提供一流的企业解决方案。这就是为什么我们可以自信地提供可用性和服务可靠性保证,同时保证容错性。