【论文阅读】Practical Byzantine Fault Tolerance

摘要

本文描述了一种新的复制算法,它能够容忍拜占庭错误。我们相信拜占庭容错算法在未来将变得越来越重要,因为恶意攻击和软件错误越来越常见,并可能导致故障节点表现出任意行为。虽然以前的算法假设同步系统,或者太慢,无法在实际中使用,但本文描述的算法是实用的:它可以在Internet等异步环境中工作,并集成了几个重要的优化,这些优化将以前算法的响应时间提高了一个数量级。我们使用我们的算法实现了一个拜占庭容错NFS服务,并测量了它的性能。结果表明,我们的服务只比标准的未复制NFS慢3%。

1 介绍

恶意攻击和软件错误越来越普遍。工业和政府对在线信息服务的日益依赖使恶意攻击更具吸引力,并使成功攻击的后果更加严重。此外,由于软件的规模和复杂性的增长,软件错误的数量也在增加。由于恶意攻击和软件错误会导致故障节点表现出拜占庭(即任意)行为,拜占庭容错算法变得越来越重要。

本文提出了一种新的、实用的状态机复制算法[17,34],它可以容忍拜占庭式错误。该算法提供了活动性和安全性,前提是最多有13个副本同时发生故障。这意味着客户端最终会收到对其请求的答复,这些答复根据线性化性是正确的[14,4]。该算法适用于像互联网这样的异步系统,它整合了重要的优化,使其能够高效地执行。

该研究部分由DARPA支持,合同为DABT6395-C-005,由陆军华奇卡堡监视,合同为F30602-98-1-0237,由空军研究实验室监视,部分由NEC监视。Miguel Castro得到了PRAXIS XXI奖学金的部分资助。

以及容忍拜占庭式错误(从[19]开始)的复制技术。然而,大多数早期的工作(如[3,24,10])要么关注旨在证明理论可行性的技术,但这些技术效率太低,无法在实践中使用,要么假设同步,即依赖于消息延迟和进程速度的已知边界。与我们最接近的系统Rampart[30]和SecureRing[16]被设计为实用的,但它们依赖于同步假设的正确性,这在出现恶意攻击时是危险的。攻击者可以通过延迟非故障节点或它们之间的通信,直到将其标记为故障节点并将其排除在复制组之外,从而危及服务的安全性。这种拒绝服务攻击通常比获得对非故障节点的控制更容易。

我们的算法不容易受到这种类型的攻击,因为它不依赖于同步的安全性。此外,它还将Rampart和SecureRing的性能提高了一个数量级,如第7节所述。它只使用一个消息往返来执行只读操作,使用两个消息往返来执行读写操作。在正常运行过程中,采用了基于消息认证码的高效认证方案;公开密钥密码学被认为是Rampart中的主要延迟[29]和吞吐量[22]瓶颈,它只在出现故障时使用。

为了评估我们的方法,我们实现了一个复制库,并使用它实现了一个真实的服务:一个支持NFS协议的拜占庭容错分布式文件系统。我们使用Andrew基准[15]来评估系统的性能。结果表明,在正常情况下,我们的系统仅比Digital Unix内核中的标准NFS守护进程慢3%。

因此,本文做出了以下贡献:

  • 它描述了第一个在异步网络中的拜占庭故障中正确存活的状态机复制协议。

    扫描二维码关注公众号,回复: 14684088 查看本文章
  • 它描述了一些重要的优化,使算法能够很好地执行,从而可以在实际系统中使用。

  • 它描述了拜占庭容错分布式文件系统的实现。

  • 它提供了量化复制技术成本的实验结果。

本文的其余部分组织如下。

我们首先描述我们的系统模型,包括我们的故障假设。第3节描述了算法所解决的问题和正确性条件。

算法将在第4节中描述,一些重要的优化将在第5节中描述。

第6节介绍了我们的复制库以及如何使用它实现拜占庭式容错NFS。第7节给出了我们的实验结果。

第8节讨论了相关工作。最后,我们总结了我们所取得的成果,并讨论了未来的研究方向。

2 系统模型

我们假设一个异步分布式系统,其中节点通过网络连接。网络可能无法传递消息、延迟消息、复制消息或无序传递消息。

我们使用拜占庭故障模型,即故障节点可以任意行为,只受下面提到的限制。我们假设独立节点故障。

要使这种假设在存在恶意攻击的情况下成立,需要采取一些步骤,例如,每个节点应该运行服务代码和操作系统的不同实现,并且应该具有不同的根密码和不同的管理员。可以从相同的代码库[28]获得不同的实现,对于低程度的复制,可以从不同的供应商购买操作系统。n版本编程,即不同的程序员团队产生不同的实现,是一些服务的另一种选择。

我们使用加密技术来防止欺骗和重放,并检测损坏的消息。我们的消息包含公钥签名[33]、消息身份验证码[36]和抗碰撞哈希函数[32]生成的消息摘要。我们将节点签名的消息表示为,而将消息的摘要表示为。我们遵循对消息摘要进行签名并将其附加到消息的明文中,而不是对完整消息进行签名(应该以这种方式解释)的常见做法。所有副本都知道其他副本的公钥以验证签名。

我们允许一个非常强大的对手,它可以协调故障节点、延迟通信或延迟正确节点,以便对复制服务造成最大的破坏。我们确实假设对手不能无限期地延迟正确的节点。我们还假设对手(及其控制的故障节点)在计算上受到约束,因此(以非常高的概率)它无法破坏上述的加密技术。例如,对手无法生成非故障节点的有效签名,无法从摘要中计算摘要总结的信息,也无法找到具有相同摘要的两条消息。

我们使用的加密技术被认为具有这些特性[33,36,32]。

3 服务属性

我们的算法可以用来实现任何带有状态和一些操作的确定性复制服务。操作并不局限于对服务状态部分的简单读写;它们可以使用状态和操作参数执行任意确定性计算。客户端向复制的服务发出请求,以调用操作并阻塞等待应答。复制的服务是通过副本实现的。如果客户端和副本遵循第4节中的算法,并且没有攻击者可以伪造他们的签名,那么它们就是没有错误的。

假设不超过13个副本故障,该算法既提供了安全性又提供了活性。安全性意味着复制服务满足线性性14:它的行为类似于一个集中式实现,一次原子地执行一个操作。安全性要求对故障副本的数量进行限制,因为故障副本的行为可以是任意的,例如,它可以破坏自己的状态。

无论有多少故障客户端正在使用该服务(即使它们与故障副本勾结),安全性都是提供的:由故障客户端执行的所有操作都由非故障客户端以一致的方式观察。

特别是,如果服务操作被设计为保留服务状态上的某些不变量,那么有问题的客户机就不能破坏这些不变量。

安全属性不足以防范错误的客户端,例如,在文件系统中,错误的客户端可以向某些共享文件写入垃圾数据。但是,我们通过提供访问控制来限制错误客户机可能造成的损害:我们对客户机进行身份验证,如果发出请求的客户机没有调用操作的权利,则拒绝访问。此外,服务可能提供更改客户机访问权限的操作。由于该算法确保所有客户端都能一致地观察到访问撤销操作的效果,因此提供了一种强大的机制来从错误客户端的攻击中恢复。

该算法并不依赖同步性来提供安全性。因此,它必须依靠同步性来提供有效性;否则,它可以被用来在异步系统中实现共识,这是不可能的[9]。我们保证有效性,即客户最终会收到对其请求的回复,前提是最多有1 3个副本是有问题的,并且延迟的增长速度不会超过无限期。这里,延迟是指消息第一次发送的时刻和目的地收到的时刻之间的时间(假设发送者不断重发消息直到收到)。(更精确的定义可以在[4]中找到。)这是一个相当弱的同步性假设,只要网络故障最终被修复,在任何真实的系统中都可能是真实的,但它使我们能够规避[9]中的不可能结果。

我们算法的弹性是最优的:3 1是允许异步系统在多达多个副本发生故障时提供安全性和活性属性的最小副本数量(参见[2]获得证明)。之所以需要这么多副本,是因为它必须能够在与副本通信后继续进行,因为副本可能出现故障或没有响应。然而,有可能没有响应的副本没有错误,因此,那些响应的副本可能是错误的。即便如此,仍然必须有足够的响应,使来自无缺陷副本的响应数量超过来自有缺陷副本的响应数量,即2。因此3。

该算法没有解决容错隐私的问题:错误的副本可能会将信息泄露给攻击者。在一般情况下提供容错隐私是不可实现的,因为服务操作可能使用它们的参数和服务状态执行任意的计算;为了高效地执行这些操作,副本需要清楚地获得这些信息。即使存在恶意副本[13]的阈值,也可以使用秘密共享方案[35]来获得对服务操作不透明的参数和状态部分的隐私。我们计划在未来研究这些技术。

4 算法

我们的算法是状态机复制的一种形式[17,34]:服务被建模为在分布式系统的不同节点之间复制的状态机。

每个状态机副本维护服务状态并实现服务操作。我们用0 1中的一个整数来表示副本集并标识每个副本。为简单起见,我们假设3 1,其中可能出现故障的副本的最大数量为3 1;尽管可能有3个以上的1副本,但是额外的副本会降低性能(因为交换的消息更多更大),而不会提高弹性。

副本在一系列称为视图的配置中移动。在视图中,一个副本是主副本,其他副本是备份副本。视图按顺序编号。视图的主要是复制,其中mod是视图号。当主视图出现失败时,将执行视图更改。Viewstamped Replication[26]和Paxos[18]使用了类似的方法来容忍良性错误(如第8节所述)。该算法的工作原理大致如下:客户机向主服务器2发送调用服务操作的请求。主服务器将请求多播到备份服务器3。副本执行请求并向客户机4发送应答。客户端等待来自不同副本的1个响应,结果相同;这是操作的结果。

与所有状态机复制技术[34]一样,我们对副本施加了两个要求:它们必须是确定的(即,在给定状态下使用给定参数集执行操作必须总是产生相同的结果),并且必须从相同的状态开始。考虑到这两个要求,该算法通过保证所有无错误的副本都同意执行请求的总顺序来确保安全性。

本节的其余部分将描述该算法的简化版本。我们忽略了节点如何从由于空间不足而导致的故障中恢复。我们还省略了与消息重传相关的细节。

此外,我们假设使用数字签名来实现消息认证,而不是更有效的基于消息认证码的方案;第5节进一步讨论了这个问题。在[4]中给出了使用I/O自动机模型[21]的算法的详细形式化。

4.1客户端

客户端通过向主端发送REQUEST消息来请求执行状态机操作。时间戳用于确保执行客户端请求的语义完全一次。

请求的时间戳是完全有序的,这样之后的请求比之前的请求有更高的时间戳;例如,时间戳可以是发出请求时客户机本地时钟的值。

副本发送到客户机的每个消息都包含当前视图号,允许客户机跟踪视图,从而跟踪当前主视图。客户机使用点对点消息向它认为是当前主服务器的对象发送请求。主服务器使用下一节描述的协议将请求原子地多播到所有备份。

副本将对请求的应答直接发送到客户机。应答的形式是reply,其中是当前视图编号,是对应请求的时间戳,是副本编号,是执行请求操作的结果。

客户端在接受结果之前,会等待1个来自不同副本的有效签名的回复,并且具有相同的和,。这确保了结果是有效的,因为最多副本可能是有问题的。

如果客户端没有很快收到回复,它就会广播请求到所有副本。如果请求已经被处理,副本只需重新发送应答;副本会记住它们发送给每个客户机的最后一个应答消息。否则,如果副本不是主副本,它将把请求转发给主副本。如果主服务器没有将请求多播到组,那么它最终会被足够多的副本怀疑是错误的,从而导致视图更改。

在本文中,我们假设客户端在发送下一个请求之前等待一个请求完成。但是我们可以允许客户端发出异步请求,同时保留对它们的顺序约束。

4.2 通常情况下操作

每个副本的状态包括服务状态、包含副本已接受消息的消息日志和表示副本当前视图的整数。我们将在第4.3节中描述如何截断日志。

当主服务器接收到客户端请求时,它启动一个三相协议,以原子方式将请求多播到副本。主服务器立即启动协议,除非协议正在进行的消息数量超过了给定的最大值。在本例中,它缓冲请求。缓冲的请求稍后作为一个组进行多播,以减少在高负载下的消息流量和CPU开销;此优化类似于事务性系统[11]中的组提交。为了简单起见,我们在下面的描述中忽略这个优化。

这三个阶段分别是预准备、准备和提交。

预准备和准备阶段用于对同一视图中发送的请求进行完全排序,即使提议请求排序的主视图发生故障。准备和提交阶段用于确保提交的请求在视图之间完全有序。

在预准备阶段,主服务器为请求分配一个序列号,将预准备消息多播到所有备份,并将该消息附加到其日志中。消息的形式为PRE-PREPARE,其中表示发送消息的视图、客户机的请求消息和摘要。

请求不包括在预准备消息中,以保持它们的小。这很重要,因为preprepare消息被用作在视图更改中为请求分配序列号的证明。

此外,它对协议进行解耦,以完全从协议中对请求进行排序,从而将请求传输到副本;允许我们对协议消息使用针对小消息优化的传输,对大型请求使用针对大消息优化的传输。

备份接受提供的预准备消息:请求和预准备消息中的签名是正确的,并且是的摘要;它在视野中;它没有接受包含不同摘要的视图和序列号的预准备消息;预准备消息中的序列号介于低水位标记和高水位标记之间。

最后一个条件通过选择一个非常大的序列号来防止错误的主节点耗尽序列号空间。我们将在4.3节讨论如何推进。

如果backup接受PRE-PREPARE消息,它将通过向所有其他副本多播一条prepare消息进入准备阶段,并将这两条消息添加到它的日志中。否则,它什么也不做。

副本(包括主副本)接受准备消息并将其添加到其日志中,前提是它们的签名是正确的,它们的视图号等于副本的当前视图,并且它们的序列号介于和之间。

当且仅当replica在其日志中插入了请求、带有序列号的视图中的pre-prepare for和匹配pre-prepare的不同备份中的2 prepare时,我们定义谓语prepared为真。副本通过检查它们是否具有相同的视图、序列号和摘要来验证准备是否与预准备匹配。

算法的预准备和准备阶段确保无错误副本对视图中请求的总顺序达成一致。更准确地说,它们确保了以下不变量:如果prepared为真,那么对于任何非错误副本(包括),prepared为假。这是正确的,因为prepared和3 1意味着至少有一个没有错误的副本发送了一个带有序列号的pre-prepare或prepare for视图。因此,for prepared为真,这些副本中至少有一个需要发送两个冲突的prepare(如果是主for则为预准备),即两个具有相同视图和序列号和不同摘要的prepare。但这是不可能的,因为副本没有故障。最后,我们关于消息摘要强度的假设确保了和的概率可以忽略不计。

副本在准备时将COMMIT多播到其他副本。

这将开始提交阶段。副本接受提交消息,并在其日志中插入它们,前提是它们被正确签名,消息中的视图号等于副本的当前视图,序列号介于和之间

我们将committed和committed-local谓词定义如下:当且仅当prepared对1个无故障副本的某个集合中的所有副本为true时,committed为true;而committed-local当且仅当prepared为真并接受了来自匹配preprepare for的不同副本的2个1提交(可能包括它自己的)时为真;如果它们具有相同的视图、序列号和摘要,则提交与预准备匹配。

提交阶段确保以下不变量:如果committed-local对于某些无故障的情况为真,则committed为真。这个不变量和第4.4节中描述的视图更改协议确保非故障副本对本地提交的请求的序列号达成一致,即使它们在每个副本的不同视图中提交。此外,它还确保在一个无错误副本上本地提交的任何请求最终将在一个或多个无错误副本上提交。

每个副本执行after committed-local请求的操作为true,并且它的状态反映了所有序列号较低的请求的顺序执行。这确保了所有没有故障的副本按照提供安全属性所需的相同顺序执行请求。在执行请求的操作之后,副本向客户端发送应答。

副本丢弃时间戳低于它们发送给客户机的最后一个应答中的时间戳的请求,以保证精确一次的语义。

我们不依赖于有序的消息传递,因此副本可能会乱序提交请求。这并不重要,因为它将记录preprepare、prepare和commit消息,直到执行相应的请求。

图1显示了在无主要故障的正常情况下算法的操作。副本0为主端,副本3故障,副本3为客户端。

在这里插入图片描述

4.3垃圾收集

本节讨论丢弃日志消息的机制。为了保持安全条件,消息必须保存在副本的日志中,直到它知道它们所关注的请求已经被至少一个没有错误的副本执行,并且它可以向视图更改中的其他副本证明这一点。此外,如果某个副本错过了被所有非错误副本丢弃的消息,则需要通过传输全部或部分服务状态来更新该副本。因此,副本也需要一些证明状态是正确的证据。

在执行每个操作之后生成这些证明将是昂贵的。相反,它们是周期性地生成的,当一个请求的序号可以被某个常数整除(例如,100)时执行。我们将这些请求的执行所产生的状态称为检查点,我们将说带有证明的检查点是稳定的检查点。

副本维护服务状态的多个逻辑副本:最后一个稳定检查点、零个或多个不稳定检查点以及当前状态。如6.3节所述,可以使用写时复制技术来减少存储额外状态副本的空间开销。

检查点的正确性证明如下所示。当一个副本产生一个检查点时,它向其他副本多播一个消息checkpoint,其中是最后一个请求的序列号,该请求的执行反映在状态中,是状态的摘要。每个副本在它的日志中收集检查点消息,直到它有2 1个由不同副本签名的相同摘要的序列号(可能包括它自己的这样的消息)。这两个1消息是检查点正确性的证明。

带有证明的检查点变得稳定,副本丢弃其日志中序列号小于或等于的所有pre-prepare、prepare和commit消息;它还丢弃所有较早的检查点和检查点消息。

计算证明是有效的,因为可以使用第6.3节讨论的增量加密[1]计算摘要,而且很少生成证明。

检查点协议用于提高低水位和高水位标记(这限制了将接受什么消息)。低水位标记等于最后一个稳定检查点的序列号。高水位线,这个水位线足够大,所以副本不会因为等待检查点变得稳定而停滞。例如,如果每100个请求执行一次检查点,则可能是200个。

4.4 视图更改

视图更改协议通过允许系统在主系统故障时取得进展而提供灵活性。视图变化由超时触发,防止备份无限期地等待请求的执行。如果一个备份收到了一个有效的请求,但还没有执行它,那么它就在等待一个请求。当一个备份收到一个请求,并且该定时器还没有运行时,它就会启动一个定时器。 当它不再等待执行请求时,它就会停止定时器,但如果此时它正在等待执行其他请求,就会重新启动定时器。

如果视图中的备份定时器过期,则备份启动视图变更,将系统移至视图1。它停止接受消息(除了检查点、视图更改和新视图消息),并向所有副本多播一个view-change 1消息。这里是已知的最后一个稳定检查点的序列号,是一组由2个1个有效检查点消息组成的集合,证明的正确性,并且是一个集合,包含用于每个请求的集合,其序列号大于。每个集合包含一个有效的预准备消息(不包含相应的客户端消息)和两个匹配的、有效的准备消息,由具有相同视图、序列号和摘要的不同备份签名。

当视图1的主视图从其他副本接收到2个有效的视图更改消息时,它向所有其他副本多播一个NEW-VIEW 1消息,其中一个集合包含主视图接收到的有效视图更改消息加上主视图发送(或将发送)的1个视图更改消息,并且是一组预准备消息(没有承载请求)。计算公式如下:1。中的最新稳定检查点的序列号min-s和准备消息中的最高序列号max-s。

  1. 主进程为min-s和max-s之间的每个序列号为视图1创建一个新的预准备消息。有两种情况:(1)在具有序列号的某个视图更改消息的组件中至少有一个集合,或者(2)没有这样的集合。在第一种情况下,主服务器创建一个新消息PRE-PREPARE 1,其中是预准备消息中的请求摘要,其序列号中视图号最高。在第二种情况下,它创建一个新的预准备消息PRE-PREPARE 1,其中是一个特殊空请求的摘要;空请求像其他请求一样通过协议,但它的执行是无操作的。(Paxos[18]使用了类似的技术来填补空白。)接下来,主服务器将消息追加到其日志中。如果min-s大于其最新稳定检查点的序列号,主检查点也会在其日志中插入序列号为min-s的检查点的稳定性证明,并丢弃日志中的信息(详见4.3节)。然后它进入视图1:此时它能够接受视图1的消息。

如果视图1的新视图消息是正确签名的,如果其中包含的视图更改消息对视图1有效,并且集合正确,则备份将接受视图1的新视图消息;它通过执行类似于主进程用于创建的计算来验证的正确性。然后,它将新信息添加到它的日志中,就像对主服务器所描述的那样,将每个消息的prepare多播到所有其他副本中,将这些prepare添加到它的日志中,并进入视图1。

此后,协议按照第4.2节所述进行。副本对min-s和max-s之间的消息重做协议,但它们避免重新执行客户机请求(通过使用它们存储的关于发送给每个客户机的最后一个应答的信息)。

副本可能缺少一些请求消息或稳定检查点(因为这些不会在newview消息中发送)。它可以从另一个副本中获取丢失的信息。例如,副本可以从其中一个副本获得缺失的检查点状态,该副本的检查点消息验证了它的正确性。由于这些副本中有一个是正确的,副本将始终获得或稍后认证的稳定检查点。我们可以通过对状态进行分区并使用修改它的最后一个请求的序列号戳戳每个分区来避免发送整个检查点。要使副本更新,只需将过期的分区发送给它,而不必发送整个检查点。

4.5 正确性

本节简要说明了算法提供安全性和活跃性的证明;详细信息可在[4]中找到。

4.5.1安全性

如前所述,如果所有无故障的副本都同意本地提交的请求的序列号,则算法提供了安全性。

在第4.2节中,我们演示了如果prepared为true,那么对于任何非错误副本(包括)和任何这样的副本,prepared为false。这意味着两个无故障的副本就在两个副本的同一视图中本地提交的请求的序列号达成一致。

视图更改协议确保无故障的副本也同意在不同副本的不同视图中本地提交的请求的序列号。

只有当提交为true时,请求才会在视图中显示序列号的非故障副本上本地提交。这意味着存在一个集合1,其中包含至少1个非故障副本,对于集合中的每个副本,prepared都为真。

非故障复制体在没有收到new-view消息的情况下不会接受pre-prepare for view(因为只有在这个时候它们才会进入view)。但是,任何正确的视图新消息都包含来自2个1副本的集合2中的每个副本的正确视图变化消息。由于有3个1副本,1和2必须至少在一个没有问题的副本中相交。在第一种情况下,算法会重新进行原子组播协议的三个阶段,以相同的序列号和新的视图号。这一点很重要,因为它可以防止任何在以前的视图中被分配了序列号的不同请求提交。在第二种情况下,新视图中的任何副本都不会接受任何序列号低于.的消息。 在任何一种情况下,各副本都会同意在本地提交序列号为.的请求。

4.5.2 活性

为了提供活跃性,如果副本无法执行请求,它们必须移动到一个新的视图。但重要的是要最大化同一视图中至少有2个1个无故障副本的时间段,并确保这段时间呈指数增长,直到执行某个请求的操作。我们通过三种方式实现这些目标。

首先,为了避免过早地开始视图更改,为视图1多播视图更改消息的副本等待视图1的2个视图更改消息,然后启动计时器在一段时间后过期。

如果计时器在它接收到有效的new-view消息1之前或者在它执行之前没有执行的新视图请求之前过期,它将开始视图2的视图更改,但这一次它将等待2,然后开始视图3的视图更改。

其次,如果一个副本从其他副本接收到一组有效的视图更改消息(对于大于其当前视图的视图),它将为集合中最小的视图发送一个视图更改消息,即使它的计时器没有过期;这可以防止它太晚开始下一个视图更改。

第三,错误的副本不能通过强制频繁更改视图来阻碍进度。一个错误的副本不能通过发送视图更改消息导致视图更改,因为只有至少一个副本发送视图更改消息时视图更改才会发生,但是当它是主副本时(通过不发送消息或发送错误消息)它可以导致视图更改。但是,因为视图的主视图是副本,所以mod的主视图不能连续多个视图出现故障。

这三种技术保证了活跃性,除非消息延迟的增长速度无限地快于超时时间,而这在实际系统中是不可能的。

4.6 非确定性

状态机副本必须是确定性的,但许多服务都涉及某种形式的非确定性。例如,NFS中的time-last-modified是通过读取服务器的本地时钟设置的;如果在每个副本上都独立地执行这一操作,则无故障副本的状态将会偏离。因此,需要某种机制来确保所有副本选择相同的值。通常,客户端无法选择值,因为它没有足够的信息;例如,它不知道相对于其他客户机的并发请求,它的请求将如何排序。相反,主服务器需要独立地选择值,或者根据备份提供的值选择值。

如果主服务器独立地选择非确定性值,它将该值与相关的请求连接起来,并执行三相协议,以确保没有故障的副本同意请求和值的序列号。这可以通过向不同的副本发送不同的值来防止错误的主节点导致副本状态发散。然而,错误的主节点可能向所有副本发送相同的、不正确的值。因此,副本必须能够仅基于服务状态确定地决定值是否正确(以及如果不正确应该做什么)。

该协议适用于大多数服务(包括NFS),但有时副本必须参与选择值以满足服务的规范。

这可以通过在协议中添加一个额外的阶段来实现:主服务器获取备份服务器提出的经过身份验证的值,将其中的2 1与关联的请求连接起来,并为连接的消息启动三相协议。副本通过对21个1值及其状态的确定性计算来选择值,例如取中值。在一般情况下,额外的相位可以被优化掉。例如,如果副本需要一个“足够接近”其本地时钟的值,那么当它们的时钟在某个增量内同步时,就可以避免额外的相位。

5 优化介绍

在正常情况下改进算法性能的一些优化。所有的优化都保持了活跃性和安全性。

5.1 减少交流

我们使用三种优化方法来降低通信成本。首先,避免了发送大多数大型回复。客户端请求指定一个副本来发送结果;所有其他副本发送的回复只包含结果的摘要。摘要允许客户端检查结果的正确性,同时大大减少网络带宽消耗和大型回复的CPU开销。如果客户端没有从指定的副本中收到正确的结果,它就会像往常一样重新传输请求,要求所有的副本发送完整的回复。

第二个优化将操作调用的消息延迟数从5减少到4。一旦准备好的谓词为请求保留,副本就会试探性地执行请求,它们的状态反映了所有序号较低的请求的执行,并且这些请求都已知已提交。在执行请求之后,副本向客户机发送暂定答复。客户端等待21个匹配的临时答复。如果它接收到这么多,则保证请求最终提交。

否则,客户端重新传输请求并等待1个非试探性答复。

如果视图发生变化,已暂时执行的请求可能会中止,并被空请求替换。在这种情况下,副本将其状态恢复到新视图消息中的最后一个稳定检查点状态或它的最后一个检查点状态(取决于哪个具有更高的序列号)。

第三个优化改进了不修改服务状态的只读操作的性能。客户端向所有副本多播一个只读请求。副本在检查请求是否正确地进行了身份验证、客户机具有访问权限以及请求实际上是只读的之后,将立即以试探性状态执行请求。只有在临时状态中反映的所有请求都已提交之后,它们才发送应答;这对于防止客户端观察到未提交状态是必要的。客户端等待来自不同副本的2个1响应,结果相同。如果有影响结果的并发数据写入,客户端可能无法收集2 1这样的响应;在这种情况下,它将在重传计时器过期后将请求作为常规的读写请求重传。

5.2 加密

在第4节中,我们描述了一种使用数字签名验证所有消息的算法。然而,我们实际上只对很少发送的视图更改和新视图消息使用数字签名,并使用消息身份验证码(mac)验证所有其他消息。这消除了以前系统的主要性能瓶颈[29,22]。

然而,mac相对于数字签名有一个基本的限制——无法证明消息对第三方是真实的。第4节中的算法和之前用于状态机复制的拜占庭容错算法[31,16]依赖于数字签名的额外能力。我们修改了我们的算法,通过利用特定的不变量来规避这个问题,例如,不允许两个不同的请求在两个没有错误的副本上准备相同的视图和序号的不变量。修改后的算法在[5]中描述。这里我们简要介绍一下使用mac的主要含义。

mac的计算速度比数字签名快三个数量级。例如,200MHz的Pentium Pro生成一个MD5摘要的1024位模RSA签名需要43毫秒,验证签名[37]需要0.6毫秒,而在我们的实现中,在相同的硬件上计算一个64字节消息的MAC只需要10.3秒。还有其他的公钥密码系统生成签名更快,例如椭圆曲线公钥密码系统,但签名验证较慢[37],在我们的算法中,每个签名都要验证多次。

每个节点(包括活动客户端)与每个副本共享一个16字节的秘密会话密钥。我们通过将MD5应用于消息与密钥的连接来计算消息认证码。我们不使用最终MD5摘要的16个字节,而是只使用最低有效字节的10个。这种截断有一个明显的优点,那就是减少mac的大小,并且它还提高了它们对某些攻击的恢复能力[27]。

这是秘密后缀方法[36]的变体,只要MD5是抗冲突的,它就是安全的[27,8]。

回复消息中的数字签名被单个MAC替换,这就足够了,因为这些消息只有一个预期的收件人。所有其他消息中的签名(包括客户端请求,但不包括视图更改)都被我们称为身份验证器的mac向量所取代。验证方对除发送方以外的每个副本都有一个条目;每个表项都是用发送方共享的密钥和对应的副本计算出的MAC。

验证身份验证器的时间是恒定的,但生成一个身份验证器的时间随副本的数量线性增长。这不是问题,因为我们不期望有大量的副本,而且MAC和数字签名计算之间存在巨大的性能差距。此外,我们高效地计算验证者;对消息应用MD5一次,并通过对相应的会话密钥应用MD5,使用产生的上下文计算每个向量项。例如,在一个拥有37个副本的系统中(即,一个可以容忍12个同时故障的系统),验证者的计算速度仍然比1024位模数RSA签名快两个数量级。

身份验证器的大小随着副本的数量线性增长,但增长缓慢:它等于30 1 3个字节。验证器比RSA签名小,具有1024位模数为13(即,系统可以容忍最多4个同时故障),我们期望在大多数配置中都是这样。

6 实现

本节描述我们的实现。首先我们讨论复制库,它可以用作任何复制服务的基础。在第6.2节中,我们将介绍如何在复制库之上实现一个复制的NFS。然后我们描述了如何维护检查点并有效地计算检查点摘要。

复制库的客户端接口由单个过程、调用(带一个参数)、输入缓冲区(包含调用状态机操作的请求)组成。调用过程使用我们的协议在副本上执行请求的操作,并从各个副本的应答中选择正确的应答。它返回一个指向包含操作结果的缓冲区的指针。

6.1 复制库

在服务器端,复制代码对应用程序的服务器部分必须实现的过程进行多次上调用。有执行请求(执行)、维护服务状态检查点(创建检查点、删除检查点)、获取指定检查点的摘要(获取摘要)以及获取丢失的信息(获取检查点、设置检查点)的过程。执行过程接收一个包含所请求操作的缓冲区作为输入,执行该操作,并将结果放在输出缓冲区中。其他程序将在第6.3和6.4节中进一步讨论。

节点之间的点对点通信使用UDP实现,副本组播使用UDP在IP组播[7]上实现。每个服务都有一个IP多播组,其中包含所有副本。这些通信协议是不可靠的;他们可能会复制或丢失消息,或发送错误的消息。

该算法容忍乱序交付并拒绝重复。视图更改可用于从丢失的消息中恢复,但这代价昂贵,因此执行重传非常重要。在正常操作期间,从丢失的消息中恢复是由接收者驱动的:备份在过期时向主服务器发送否定的确认,主服务器在长时间超时后重新发送预准备消息。对否定确认的回复可能包括稳定检查点的一部分和丢失的消息。在视图更改期间,副本会重传视图更改消息,直到它们接收到匹配的newview消息,或者它们转移到后面的视图。

复制库目前不实现视图更改或重传输。这不会影响第7节中给出的结果的准确性,因为算法的其余部分已经完全实现(包括触发视图更改的计时器的操作),而且我们已经形式化了完整的算法,并证明了它的正确性[4]。

6.2 BFS:拜占庭容错文件系统

我们使用复制库实现了BFS,一种拜占庭容错NFS服务。图2显示了BFS的体系结构。我们选择不修改内核NFS客户机和服务器,因为我们没有Digital Unix内核的源代码。

由容错NFS服务导出的文件系统像任何常规NFS文件系统一样挂载在客户端机器上。应用程序进程未经修改地运行,并通过内核中的NFS客户机与挂载的文件系统进行交互。我们依靠用户级中继进程来协调标准NFS客户机和副本之间的通信。一个中继接收NFS协议请求,调用我们的复制库的调用过程,并将结果发送回NFS客户机。

在这里插入图片描述
每个副本使用复制库和我们的NFS V2守护进程运行一个用户级进程,我们将其称为snfsd(用于简单的nfsd)。复制库从中继接收请求,通过向上调用与snfsd进行交互,并将NFS响应打包成发送给中继的复制协议响应。

我们使用固定大小的内存映射文件实现了snfsd。所有的文件系统数据结构,例如索引节点,块和它们的空闲列表,都在映射文件中。

我们依赖操作系统来管理内存映射文件页的缓存,并异步地将修改的页写入磁盘。当前的实现使用8KB的块,inode包含NFS状态信息和256字节的数据,用于存储目录中的目录条目、文件中的块指针和符号链接中的文本。目录和文件也可能以类似于Unix的方式使用间接块。

我们的实现确保所有的状态机副本都从相同的初始状态开始,并且是确定性的,这是使用我们的协议实现的服务的正确性的必要条件。主机提出最后修改时间和最后访问时间的值,复制机选择所提出的值和大于为早期请求选择的所有值的最大值中较大的一个。我们不需要同步写入来实现NFS V2协议的语义,因为BFS通过复制实现了修改数据和元数据的稳定性[20]。

6.3 维护检查点

介绍snfsd如何维护文件系统状态检查点。回想一下,每个副本都维护状态的几个逻辑副本:当前状态、一些尚未稳定的检查点和最后一个稳定检查点。

SNFSD直接在内存映射文件中执行文件系统操作,以保持位置,并且它使用copyon-write来减少与维护检查点相关的空间和时间开销。SNFSD为内存映射文件中的每个512字节块维护一个copyon-write位。当复制代码调用make检查点上调用时,snfsd设置所有写时复制位并创建一个(易变的)检查点记录,其中包含当前序列号(作为上调用的参数接收)和一个块列表。该列表包含自检查点被执行以来修改的块的副本,因此,它最初是空的。记录还包含当前状态的摘要;我们将在第6.4节讨论如何计算摘要。

当在执行客户机请求时修改内存映射文件的块时,snfsd为该块检查copy- write位,如果设置了该位,则在最后一个检查点的检查点记录中存储该块的当前内容及其标识符。然后,它用它的新值覆盖该块,并重置其写时复制位。

SNFSD保留检查点记录,直到被告知通过删除检查点的上调用丢弃它,该上调用是在后面的检查点变得稳定时由复制代码执行的。

如果复制代码需要一个检查点发送到另一个副本,它调用get检查点上行调用。为了获得一个块的值,snfsd首先在稳定检查点的检查点记录中搜索该块,然后搜索以后检查点的检查点记录。如果块不在任何检查点记录中,它将从当前状态返回值。

使用write -on-write技术以及我们最多保留2个检查点的事实确保了保持状态的多个逻辑副本的空间和时间开销很低。例如,在第7节描述的Andrew基准测试实验中,平均检查点记录大小只有182块,最多500块。

6.4 计算检查点摘要

SNFSD计算检查点状态的摘要,作为make检查点上行调用的一部分。尽管检查点只是偶尔执行,但是由于状态可能很大,因此递增地计算状态摘要非常重要。snfsd使用一个名为AdHash[1]的抗冲突增量单向哈希函数。该函数将状态划分为固定大小的块,并使用其他一些散列函数(例如,MD5)计算通过将块索引与每个块的块值连接而获得的字符串摘要。状态摘要是块的摘要之和除以某个大整数。在我们当前的实现中,我们使用来自copy-on-write技术的512字节块,并使用MD5计算它们的摘要。

为了增量地计算状态摘要,snfsd维护了一个表,每个512字节的块都有一个哈希值。这个哈希值是通过对块索引应用MD5来获得的,该块索引与上一次检查点时的块值相连。当调用make检查点时,snfsd获取前一个检查点状态的摘要(来自相关的检查点记录)。

通过对与当前块值相连的块索引应用MD5,它为其copyon-write位被重置的每个块计算新的哈希值。然后,它将新的哈希值添加到,减去旧的哈希值,并更新表以包含新的哈希值。如果修改块的数量很小,这个过程是有效的;如上所述,Andrew基准测试的每个检查点平均修改182个块。

7 绩效评估

本节使用两个基准测试评估系统的性能:一个微基准测试和Andrew基准测试[15]。微基准提供了独立于服务的复制库性能评估;它度量调用空操作(即什么都不做的操作)的延迟。

Andrew基准测试用于将BFS与其他两个文件系统进行比较:一个是Digital Unix中的NFS V2实现,另一个与BFS相同,只是没有复制。第一个比较表明我们的系统是实用的,它的延迟与许多用户每天使用的商业系统的延迟相似。第二个比较允许我们在实际服务的实现中准确地评估算法的开销。

7.1 实验装置

实验测量了正常情况下的行为(即没有视图变化),因为这是决定系统性能的行为。所有的实验都是在一个客户端运行两个中继进程,以及四个副本的情况下进行的。四个副本可以容忍一个拜占庭式故障;我们希望这个可靠性水平对大多数应用来说是足够的。复制体和客户端在相同的DEC 3000/400 Alpha工作站上运行。

这些工作站有一个133MHz的Alpha 21064处理器,128MB的内存,并运行Digital Unix 4.0版本。文件系统由每个副本存储在一个DEC RZ26磁盘上。所有的工作站都由一个10Mbit/s的交换式以太网连接,并有DEC LANCE以太网接口。交换机是一个DEC EtherWORKS 8T/TX。实验是在一个隔离的网络上进行的。

检查点之间的间隔是128个请求,这导致在任何实验中都要进行多次垃圾收集。副本在预准备消息中接受的最大序列号为256加上最后一个稳定检查点的序列号。

7.2 微型基准测试

微基准测试度量调用空操作的延迟。它评估一个没有状态的简单服务的两个实现的性能,该服务实现了具有不同大小参数和结果的空操作。第一个实现是使用我们的库复制的,第二个是不复制的,直接使用UDP。表1报告了在客户机上为只读和读写操作测量的响应时间。它们是通过在三次不同的运行中计时10,000个操作调用获得的,我们报告了这三次运行的中值。与中位数的最大偏差总是低于报告值的0.3%。我们用a/b表示每个操作,其中a和b是操作参数和结果的大小,单位为KBytes。

在这里插入图片描述
复制库带来的开销是由于额外的计算和通信。例如,读写0/0操作的计算开销大约为1.06ms,其中包括执行加密操作所花费的0.55ms。剩余的1.47ms开销是由于额外的通信;复制库引入了一个额外的消息往返,它发送更大的消息,并且它增加了每个节点相对于没有复制的服务接收到的消息数量。

只读操作的开销显著降低,因为第5.1节中讨论的优化减少了计算和通信开销。

例如,只读0/0操作的计算开销约为0.43ms,其中包括执行加密操作所花费的0.23ms,而通信开销仅为0.37ms,因为执行只读操作的协议使用单一往返。

表1显示了4/0和0/4操作的相对开销更低。这是因为复制库引入的很大一部分开销与操作参数和结果的大小无关。例如,在读写0/4操作中,大消息(应答)只通过网络一次(如5.1节所述),并且只增加了处理应答消息的加密开销。读写4/0操作的开销更高,因为大消息(请求)经过网络两次,增加了处理请求和预准备消息的加密开销。

需要注意的是,这个微基准测试代表了我们算法的最坏情况开销,因为操作不执行任何工作,且未复制的服务器提供非常弱的保证。

大多数服务将需要更强的保证,例如,经过身份验证的连接,并且相对于实现这些保证的服务器,我们的算法引入的开销将更低。例如,相对于使用mac进行身份验证的未复制服务版本,对于读写0/0操作,复制库的开销仅为243%,而对于只读4/0操作,复制库的开销仅为4%。

我们可以对我们的算法相对于Rampart[30]所带来的性能提升估计一个粗略的下限。Reiter报告说,Rampart在4个SparcStation 10s的10Mbit/s以太网网络中,对一个空信息进行多RPC的延迟为45ms[30]。多RPC对于主程序调用一个状态机操作来说是足够的,但是对于一个任意的客户端调用一个操作来说,有必要增加一个额外的消息延迟和一个额外的RSA签名和验证来验证客户端;这将导致至少65ms的延迟(使用[29]中报告的RSA计时)。 即使我们把这个延迟除以1.7,即DEC 3000/400和SparcStation 10的SPECint92评级的比率,我们的算法仍然将调用读写和只读0/0操作的延迟分别减少了10倍和20倍以上的因素。请注意,这个比例是保守的,因为网络占了Rampart延迟的很大一部分[29],而且Rampart的结果是使用300位模数的RSA签名获得的,除非用于生成签名的密钥被频繁刷新,否则今天的签名是不安全的。

没有公布SecureRing[16]的性能数字,但它会比Rampart慢,因为它的算法在关键路径中有更多的消息延迟和签名操作。

7.3 安德鲁基准

Andrew基准测试[15]模拟软件开发工作负载。它分为五个阶段:(1)递归地创建子目录;(2)复制源树;(3)检查树中所有文件的状态,而不检查它们的数据;(4)检查所有文件中数据的每个字节;(5)对文件进行编译和链接。

我们使用Andrew基准测试将BFS与其他两个文件系统配置进行比较:NFS-std,这是Digital Unix中的NFS V2实现,BFS-nr与BFS相同,但没有复制。BFS-nr在客户机上运行两个简单的UDP中继,在服务器上运行一个与snfsd版本链接的薄贴面,其中所有检查点管理代码都被删除了。

此配置在回复客户端之前不将修改后的文件系统状态写入磁盘。因此,它不实现NFS V2协议语义,而BFS和NFS-std都实现了。

在NFS V2协议中的18个操作中,只有getattr是只读的,因为文件和目录的时间-最后访问属性是由只读的操作设置的,例如读取和查找。结果是我们对只读操作的优化很少被使用。为了展示这种优化的影响,我们还在BFS的第二个版本上运行了Andrew基准测试,该版本将查找操作修改为只读。这种修改违反了严格的Unix文件系统语义,但在实践中不太可能产生不利影响。

对于所有配置,实际的基准测试代码在客户机工作站上运行,使用Digital Unix内核中的标准NFS客户机实现,并具有相同的挂载选项。这些与基准测试最相关的选项是:UDP传输、4096字节的读写缓冲区、允许异步客户机写和允许属性缓存。

我们报告每个配置的基准测试10次运行的平均值。运行基准的总时间的样本标准差总是低于报告值的2.6%,但在前四个阶段的个别时间的样本标准差高达14%。这种高差异也存在于NFS-std配置中。

报告平均值的估计误差在个别阶段低于4.5%,在总阶段低于0.8%。

表2显示了BFS和BFS-nr的结果。 BFS-strict和BFS-nr之间的比较表明,该服务的拜占庭容错的开销很低–BFS-strict运行完整的基准测试只需要26%的时间。这个开销比在微观基准中观察到的要低,因为客户端在操作之间花费了相当大的一部分计算时间,即在收到一个操作的回复和发出下一个请求之间,服务器上的操作进行一些计算。

但是这种开销在各个基准阶段并不统一。其主要原因是客户端在操作之间花费的计算时间的变化;前两个阶段的相对开销较高,因为客户端在操作之间花费的计算时间约占总时间的40%,而在后三个阶段花费的时间约占70%。

在这里插入图片描述
从表中可以看出,对查找应用只读优化可以显著提高BFS的性能,并将相对于BFS-nr的开销降低到20%。该优化在前四个阶段具有显著影响,因为在BFS-strict中等待查找操作完成的时间至少是这些阶段所需时间的20%,而在最后一个阶段则不到所需时间的5%。

在这里插入图片描述

表3显示了BFS vs . NFS-std的结果。这些结果表明BFS可以在实践中使用——BFSstrict只需要多3%的时间来运行完整的基准测试。因此,可以用BFS替换许多用户每天使用的数字Unix中的NFS V2实现,而不会影响这些用户感知到的延迟。此外,对查找操作进行了只读优化的BFS实际上比NFS-std快2%。

BFS相对于NFS-std的开销在所有阶段都是不一样的。两个版本的BFS在第1、2和5阶段都比NFS-std快,但在其他阶段则较慢。这是因为在第1、2和5阶段,由客户发出的大部分操作(在21%和40%之间)是同步的,即要求NFS实现在回复客户之前确保修改的文件系统状态的稳定性的操作。

NFS-std通过将修改后的状态写入磁盘来实现稳定性,而BFS则通过复制(如Harp[20])以较低的延迟来实现稳定性。NFS-std在第3和第4阶段比BFS(和BFS-nr)快,因为客户端在这些阶段没有发出同步操作。

8 相关工作

以前关于复制技术的大多数工作忽略了拜占庭错误或假设了同步系统模型(例如,[17,26,18,34,6,10])。视图戳复制[26]和Paxos[18]使用带有主和备份的视图来容忍异步系统中的良性故障。容忍拜占庭式错误需要一个更复杂的协议,包括加密身份验证、一个额外的预准备阶段和一种不同的技术来触发视图更改和选择主要内容。此外,我们的系统只使用视图更改来选择一个新的主视图,而不会像[26,18]中那样选择一组不同的副本来形成新的视图。

一些协议和共识算法容忍异步系统中的拜占庭错误(例如[2,3,24])。

然而,它们并没有为状态机复制提供完整的解决方案,而且,它们中的大多数都是为了证明理论上的可行性而设计的,在实际应用中速度太慢。在正常情况下,我们的算法类似于[2]中的拜占庭协议算法,但该算法无法在主要故障中存活。

与我们的工作最密切相关的两个系统是Rampart[29,30,31,22]和SecureRing[16]。

它们实现了状态机复制,但比我们的系统慢了一个数量级,而且最重要的是,它们依赖于同步假设。

Rampart和SecureRing都必须将错误的副本从组中排除,以取得进展(例如,删除一个错误的主节点并选择一个新的主节点),并执行垃圾收集。它们依靠故障检测器来确定哪些副本发生了故障。然而,故障检测器在异步系统[21]中不能准确,也就是说,它们可能将一个副本错误地分类为故障。

因为正确要求少于13个组成员是错误的,错误分类会从组中删除一个没有错误的副本,从而影响到正确性。这为攻击开辟了一条途径:攻击者获得了对单个副本的控制,但不以任何可检测的方式改变其行为;然后它会减慢正确的副本或它们之间的通信,直到有足够多的副本被排除出群体。

为了减少错误分类的可能性,可以对故障检测器进行校准,以延迟将副本分类为故障。然而,为了使概率可以忽略,延迟必须非常大,这是不可取的。例如,如果主服务器实际上失败了,则组将无法处理客户机请求,直到延迟过期。我们的算法不容易受到这个问题的影响,因为它从来不需要从组中排除副本。

Phalanx[23,25]应用仲裁复制技术[12]在异步系统中实现拜占庭容错。这项工作不提供通用状态机复制;相反,它提供了一个数据存储库,其中包含读写单个变量和获取锁的操作。它为读写操作提供的语义比我们的算法提供的语义弱;我们可以实现访问任意数量变量的任意操作,而在《密集阵》中,需要获取和释放锁来执行此类操作。目前还没有公布Phalanx的性能数据,但我们相信我们的算法更快,因为它在关键路径上的消息延迟更少,而且我们使用的是mac而不是公钥加密。《Phalanx》中的方法提供了改进可伸缩性的潜力;每个操作只由副本的一个子集处理。但是这种方法的可伸缩性是昂贵的:它需要4 1来容忍错误;每个副本都需要状态的一个副本;每个副本上的负载随着(它是O 1)慢慢减少。

9 结论

本文描述了一种新的状态机复制算法,它能够容忍拜占庭式错误,并可用于实践:它是第一个在像Internet这样的异步系统中正确工作的算法,它将以前的算法的性能提高了一个数量级以上。

本文还描述了BFS,一种拜占庭式的NFS容错实现。BFS证明了使用我们的算法实现与未复制服务性能相近的真实服务是可能的——BFS的性能只比Digital Unix中标准NFS实现的性能差3%。

这种良好的性能得益于许多重要的优化,包括用消息身份验证代码向量替换公钥签名、减少消息的大小和数量以及增量检查点管理技术。

拜占庭容错算法在未来很重要的一个原因是,它们可以让系统在出现软件错误时也能继续正确工作。并非所有的错误都是可以存活的;我们的方法不能掩盖发生在所有副本的软件错误。然而,它可以掩盖在不同副本上独立发生的错误,包括非确定性的软件错误,这是最有问题的持久性错误,因为它们是最难检测的。事实上,我们在运行系统时遇到了这样一个软件错误,尽管如此,我们的算法还是能够继续正确运行。

在完善我们的制度方面还有很多工作要做。一个特别感兴趣的问题是减少实现算法所需的资源量。只有当某些完整副本失败时,才可以使用副本作为协议中涉及的见证,从而减少副本的数量。我们也相信有可能将状态的副本数量减少到1,但细节仍有待制定。

猜你喜欢

转载自blog.csdn.net/miracleoa/article/details/127462218