实用拜占庭将军问题
摘要
本文提出了一种新的能够容忍拜占庭错误的复制算法。我们认为,由于恶意攻击和软件错误越来越普遍,并可能导致错误节点表现出任意行为,因此拜占庭容错算法在未来将变得越来越重要。尽管之前的算法假设是一个同步系统,或者太慢而不能在实际中使用,但是本文描述的算法是实用的:它在异步环境中工作,比如互联网,并且结合了一些重要的优化,提高了以前算法的响应时间超过一百万数量级。我们使用我们的算法实现了一个容错的拜占庭 NFS 服务,并测量了它的性能。结果表明,我们的服务只比标准的无复制 NFS 慢 3%。
1 引言
恶意攻击和软件错误越来越普遍。行业和政府对网上信息服务的依赖日益增加,使得恶意攻击更具吸引力,成功攻击的后果也更加严重。此外,由于软件的规模和复杂性的增加,软件错误的数量也在增加。由于恶意攻击和软件错误会导致错误节点表现出 Byzan-tine(即任意)行为,因此容错算法显得越来越重要。
本文提出了一种新的、实用的容忍拜占庭错误的状态机复制算[17,34]。这个算法在保证活性和安全性(liveness & safety)的前提下提供了(n-1)/3的容错性。这意味着客户最终会收到对他们请求的答复,而且根据线性化程度这些答复是正确的[14,4]。该算法适用于像互联网这样的异步系统,并且它包含了使其能够高效执行的重要优化。
在协议方面有大量的工作和容忍拜占庭错误的复制技术(从[19]开始)。然而,大多数早期的工作(例如,[3,24,10])要么涉及旨在证明理论可行性的技术,这些技术在实践中效率太低,无法使用,要么假设同步,即依赖于已知的消息延迟和进程速度界限。与我们最接近的系统 Rampart[30]和 securityering[16]被设计得很实用,但是它们依赖于同步假设来保证正确性,这在存在恶意攻击时是危险的。攻击者可能会延迟非故障节点或它们之间的通信,直到它们被标记为故障节点并被排除在复制组之外,从而危及服务的安全。这样的分布式拒绝服务攻击通常比获得一个没有故障的节点更容易。
我们的算法不容易受到这种类型的攻击,因为它不依赖于安全的同步。此外,它还提高了Rampart 和 securityering 的性能,正如第 7 节中所解释的那样,提高了不止一个数量级。**它只使用一个消息往返执行只读操作,两个消息往返执行读写操作。**此外,在正常操作中,**它使用了一种基于消息认证码的高效认证方案;**在Rampart 中,被引用为主要延迟[29]和吞吐量[22]瓶颈的公开密钥加密认证方案只在出现故障时使用。
为了评估我们的方法,我们实现了一个副本库,并用它实现了一个真正的服务:一个支持NFS 协议的拜占庭容错分散式档案系统。我们使用安德鲁基准[15]来评估我们系统的性能。结果表明,在正常情况下,我们的系统只比DigitalUnix 内核中的标准 NFS 守护进程慢 3%。因此,本文作出了以下贡献:
1.它描述了第一个能够在非同步网络中正确避免拜占庭错误的状态机复制协议。
2.它描述了一些重要的优化,使算法能够很好地执行,以便它可以在实际系统中使用。
3.它描述了一个拜占庭容错分散式档案系统的实现。
4.它提供了量化复制技术成本的实验结果。
本文的其余部分如下。我们首先描述我们的系统模型,包括我们的失败假设。第三节描述了算法所解决的问题,并说明了正确性条件。该算法内容在第 4 节中进行了描述,一些重要的优化在第 5 节中进行了描述。第 6 节描述了我们的复制库以及我们如何使用它来实现一个错误容忍的拜占庭 NFS。第七节介绍了我们的实验结果。第 8 节讨论相关工作。最后,我们总结了我们已经完成的工作,并讨论了未来的研究方向。
2 系统模型
我们假设一个异步分布式系统,其中节点通过网络连接。网络可能无法传递消息、延迟消息、重复消息或无序传递消息。
我们使用一个拜占庭式的故障模型,也就是说,错误的节点可以任意运行,只受到下面提到的限制
。我们假设独立的节点失败。为了使这个假设在恶意攻击中成立,需要采取一些步骤,例如,每个节点应该运行服务代码和操作系统的不同实现,并且应该有不同的根密码和不同的管理员
。可以从相同的代码库[28]获得不同的实现,对于低复制度,可以从不同的供应商购买操作系统。N 版本编程(即不同的程序员团队产生不同的实现)是某些服务的另一种选择。
我们使用加密技术来防止欺骗和重放并检测损坏的消息。我们的消息包含公钥签名[33]、消息验证码[36]和由抗冲突散列函数产生的消息摘要[32]
。我们将节点签名的消息表示为我们遵循一般惯例对消息摘要进行签名并将其附加到消息的明文中,而不是对完整消息进行签名消息(应该以这种方式解释)。所有的副本都知道其他副本的公钥来验证签名。
系统允许恶意节点
可以操纵多个失效节点、延迟通讯、甚至延迟正确节点来毁灭世界。但是作者限定恶意节点
不能无限期地延迟正确的节点,并且恶意节点
算力有限不能破解加密算法。例如,恶意节点
不能伪造正确节点的有效签名,不能从摘要数据反向计算出消息内容,或者找到两个有同样摘要的消息。我们使用的加密技术被认为具有这些属性[33,36,32]。
3 服务属性
这部分描述了副本复制服务的特性
我们的算法可以用来实现任何具有状态和某些操作的确定性复制服务。这些操作不限于简单地读取或写入服务状态的某些部分;它们可以使用状态和操作参数执行任意确定性计算。客户机向复制的服务发出请求,以调用操作并阻止等待应答。复制服务是通过副本实现的。如果客户机和副本遵循第 4 部分中的算法,并且没有攻击者能够伪造它们的签名,那么它们就是非错误的。
安全性
算法在失效节点数量不超过(n-1)/3的情况下同时保证安全性和活性(safety & liveness)。安全性
是指副本复制服务满足线性一致性(linearizability),就像中心化系统一样原子化执行操作。安全性要求失效副本的数量不超过上限,但是对客户端失效的数量和是否与副本串谋不做限制。系统通过访问控制来限制失效客户端可能造成的破坏,审核客户端并阻止客户端发起无权执行的操作。同时,服务可以提供操作来改变一个客户端的访问权限。因为算法保证了权限撤销操作可以被所有客户端观察到,这种方法可以提供强大的机制从失效的客户端攻击中恢复。
安全性要求对错误副本的数量进行限制,因为错误副本的行为可以是任意的,例如,它可以破坏它的状态。无论有多少有故障的客户端在使用服务,安全性都是提供的(即使他们与有故障的副本串通):有故障的客户端执行的所有操作都会被没有故障的客户端以一致的方式观察到。特别是,如果服务操作被设计为在服务状态上保留一些不变量,那么错误的客户机就不能打破这些不变量。
安全属性不足以防范错误的客户端,例如,在文件系统中,错误的客户端可以将垃圾数据写入某个共享文件。然而,通过提供访问控制,我们限制了错误客户端可能造成的损害:我们对客户端进行身份验证,并在发出请求的客户端没有权利调用操作时拒绝访问。此外,服务还可以提供更改客户端访问权限的操作。由于该算法确保访问撤销操作的效果能够被所有客户机一致地观察到,这为从错误客户机的攻击中恢复提供了一个强大的机制。
活性
算法不依赖同步提供安全性,因此必须依靠同步提供活性。否则,这个算法就可以被用来在异步系统中实现共识,而这是不可能的(由Fischer1985的论文证明)。本文的算法保证活性,即所有客户端最终都会收到针对他们请求的回复,只要失效副本的数量不超过(n-1)/3,并且延迟delay(t)不会无限增长。这个delay(t)表示t时刻发出的消息到它被目标最终接收的时间间隔,假设发送者持续重传直到消息被接收。这时一个相当弱的同步假设,因为在真实系统中网络失效最终都会被修复。但是这就规避了Fischer1985提出的异步系统无法达成共识的问题。
下面这段话是关键
本文的算法弹性是达到最优的:当存在f个失效节点时必须保证存在至少3f+1
个副本数量,这样才能保证在异步系统中提供安全性和活性。这么多数量的副本是需要的,因为在同n-f个节点通讯后系统必须做出正确判断,由于f个副本有可能失效而不发回响应。但是,有可能f个没有失效的副本不发回响应(是因为网络延迟吗?),因此f个发回响应的副本有可能是失效的。尽管如此,系统仍旧需要足够数量非失效节点的响应,并且这些非失效节点的响应数量必须超过失效节点的响应数量,即n-2f>f,因此得到n>3f。
算法不能解决信息保密的问题,失效的副本有可能将信息泄露给攻击者。在一般情况下不可能提供信息保密,因为服务操作需要使用参数和服务状态处理任意的计算,所有的副本都需要这些信息来有效执行操作。当然,还是有可能在存在恶意副本的情况下通过秘密分享模式(secret sharing scheme)来实现私密性,因为参数和部分状态对服务操作来说是不可见的。
4 算法
PBFT算法采用一种状态机复制的形式:服务被建模为一个状态机,在分布式系统中的不同节点之间复制。 每个状态机副本节点都维护服务的状态并实现服务操作。 我们用 {0, ..., |R| - 1}
中所有的整数表示一组副本节点,并使用每一个整数来标识每个副本节点。 为了简单,我们假设 |R| = 3f + 1
, f
是可能有错误的副本节点的最大数目; 尽管副本节点的总数有可能超过 3f + 1
, 但是额外的副本降低了性能(因为更多和更大的消息交换)而不会提供更高的容错能力。
副本节点通过一系列配置来移动,这些配置被称为视图。在一个视图中,一个副本节点是主节点,其他副本节点是备份节点。 视图是连续编号的。 一个视图中的主要副本的编号为 p
,由公式 p = v mod | R |
计算得来,其中 v
是视图编号。 当主节点出现故障时,将执行视图更换。Viewstamped Replication
和 Paxos
使用类似的方法来容忍良性故障(如第8节讨论的)
该算法工作流程大致如下:
- 客户端向主节点发送调用服务操作的请求。
- 主节点组播请求到各备份节点。
- 副本节点们执行请求并发送回复给客户端。
- 客户端等待来自不同副本的
f + 1
个相同的回复; 这就是这次请求的结果。
像所有状态机复制技术一样,我们对副本节点施加两个要求:它们必须是确定性的(即,在给定状态以及给定一组参数的状态下,执行操作必须始终产生相同的结果),并且它们必须以同样的状态开始服务。考虑到这两个要求,该算法通过保证即使在失败的情况下,所有无故障副本节点都会就总的执行请求的顺序达成一致,来确保安全性。
本节的其余部分介绍了该算法的简化版本。 我们忽略了节点由于缺乏存储空间而从故障中恢复的问题。 我们也省略了与消息重传有关的细节。 此外,我们假设消息认证是使用数字签名来实现的,而不是基于消息认证码的更有效的方案。 第5节将进一步讨论这个问题。 提出了使用 I / O 自动机模型的算法的详细形式化。
4.1客户端
客户端c向主节点发送<REQUEST,o,t,c>
请求执行状态机操作o,这里时间戳t用来保证客户端请求只会执行一次。客户端c发出请求的时间戳是全序排列的,后续发出的请求比早先发出的请求拥有更高的时间戳。例如,请求发起时的本地时钟值可以作为时间戳。
每个由副本节点发给客户端的消息都包含了当前的view
编号,使得客户端能够跟踪view
编号,从而进一步推算出当前主节点的编号。客户端通过点对点消息向它自己认为的主节点发送请求,然后主节点自动将该请求向所有备份节点进行广播。
副本发给客户端的响应为<REPLY,v,t,c,i,r>
,v是视图编号,t是时间戳,i是副本的编号,r是请求执行的结果。
客户端等待f+1个从不同副本得到的同样响应,同样响应需要保证签名正确,并且具有同样的时间戳t和执行结果r。这样客户端才能把r作为正确的执行结果,因为失效的副本节点不超过f个,所以f+1个副本的一致响应必定能够保证结果是正确有效的。
如果客户端没有在有限时间内收到回复,请求将向所有副本节点进行广播。如果请求已经在副本节点处理过了,副本就向客户端重发一遍执行结果。如果请求没有在副本节点处理过,该副本节点将把请求转发给主节点。如果主节点没有将该请求进行广播,那么就有认为主节点失效,如果有足够多的副本节点认为主节点失效,则会触发一次视图变更。
本文假设客户端会等待上一个请求完成才会发起下一个请求,但是只要能够保证请求顺序,可以允许请求是异步的。
4.2 PBFT算法主线流程(正常情况)
世界格局
每个副本节点的状态都包含了服务的整体状态,副本节点上的**消息日志(message log)包含了该副本节点接受(accepted)**的消息,并且使用一个整数表示副本节点的当前视图编号。我们在4.3节中描述如何截断日志。
当 primary 节点 p 接收到一个客户端请求 m 时,主节点 p 会启动一个三阶段协议,以原子方式将请求发送到其他副本。主节点立即启动协议,除非协议正在进行的消息数量超过给定的最大值。在这种情况下,它会缓冲请求。缓存的请求稍后作为一个组进行多播,以在负载较重的情况下减少消息流量和CPU开销;这种优化类似于事务性系统中的组提交。为了简单起见,我们在下面的描述中忽略了这个优化。
三个阶段分别为预准备(pre-prepare),准备(prepare)和提交(commit)。预准备和准备两个阶段用来确保同一个视图中请求发送的时序性(即使对请求进行排序的主节点失效了),准备和确认两个阶段用来确保在不同的视图之间的确认请求是严格排序的。
在预准备阶段,主要为请求分配一个序列号n,接着将预准备消息多播到所有的备份节点,并将消息添加加到它的日志中。消息的形式为<<PRE-PREPARE,v,n,d>m>
,其中 v 表示正在发送的消息所在视图的编号,m 是客户端的请求消息,d是消息m的摘要.
预准备的消息中不包括请求(可能的意思是不包含请求本身的内容),以减小预准备消息的大小。这一点很重要,因为预准备消息的目的是作为一种证明,确定该请求是在视图v中被赋予了序号n,从而在视图变更的过程中可以追索。另外一个层面,将“请求排序协议”和“请求传输协议”进行解耦,有利于对消息传输的效率进行深度优化。
只有满足以下条件,各个备份节点才会接受一个预准备消息:
1. 请求中的签名和预准备消息是正确的,并且 d 是 m 的摘要;
2. 它是在视图 v;
3. 未接受包含不同摘要的,并且视图编号为 v 和序列号为 n 的预先准备消息;
4. 预先准备消息中的序列号 n 在低水位 h 和高水位 H 之间。
条件4中的水位线存在的意义在于防止一个失效节点使用一个很大的序号消耗序号空间。我们在4.3节中讨论H和H如何前进。
进入准备阶段
如果备份节点 i 接受<<PRE-PREPARE,v,n,d>m>
预准备消息,该节点向所有副本节点发送准备消息<PREPARE,v,n,d>
从而进入准备阶段,并且将预准备消息和准备消息写入自己的消息日志。否则,它什么都不做。
接受准备消息需要满足的条件
包括主节点在内的所有副本节点在收到准备消息之后,对消息的签名是否正确,视图编号是否一致,以及消息序号是否介于h和H之间,这三个条件进行验证,如果验证通过副本(包括主节点)便接受准备消息,并将它们添加到日志中.
准备阶段完成的标志
我们定义准备阶段完成的标志为副本节点i将prepared(m, v, n, i)
记入其消息日志,其中m是请求内容,预准备消息m在视图v中的编号n,以及2f
个从不同副本节点收到的与预准备消息一致的准备消息。每个副本节点验证预准备和准备消息的一致性主要检查:视图编号v、消息序号n和摘要d。
算法的预准备和准备阶段保证无故障的副本们在视图内的请求的总排序上达成一致。更精确地说,它们确保了以下不变:如果prepared(m, v, n, i)
为真,那么对于任何非故障的副本 j,prepared(m', v, n, j)
和任何 m'
使得 D(m') ≠ D(m)
都是错误的。这是真的,因为prepared(m', v, n, i)
和| R | = 3f + 1
意味着至少有f + 1
个非故障复制已经发送了一个预先准备或准备在顺序号为n的视图中。因此,对于 prepared(m', v, n, i)
为真的这些副本中至少有一个需要发送两个冲突的准备消息(或者预准备消息,如果它是 v的主要准备),即两个准备消息具有相同的视图号和序列号以及不同的摘要。但这是不可能的,因为副本没有错误。最后,我们关于消息摘要强度的假设保证了 m ≠ m
和 D(m') ≠ D(m)
的概率是可以忽略的。
进入确认阶段
当(m,v,n,i)条件为真的时候,副本i将<COMMIT,v,n,D(m),i>
向其他副本节点广播,于是就进入了确认阶段。每个副本接受确认消息的条件是:1)签名正确;2)消息的视图编号与节点的当前视图编号一致;3)消息的序号n满足水线条件,在h和H之间。一旦确认消息的接受条件满足了,则该副本节点将确认消息写入消息日志中。(补充:需要将针对某个请求的所有接受的消息写入日志,这个日志可以是在内存中的)。
接受确认消息需要满足的条件
我们定义如下 committed 和 committed-local:当且仅当在某些集合中,对于所有的 i,committed(m, v, n)
是真的,i 已经接受了 2f + 1
非故障重复;committed-local(m, v, n, i)
从不同的副本提交(可能包括它自己的),这些副本与预准备消息中的 m 相匹配;如果具有相同的视图,序列号和摘要,则提交与预准备相匹配。
确认被接受的形式化描述
提交阶段确保以下不变式(invariant):对于正常的节点 i
来说,如果committed-local(m, v, n, i)
是真的,那么 committed(m, v, n)
是真的。第4.4节中介绍的这种不变式和视图更改协议保证了所有正常节点对本地确认的请求的序号达成一致,即使每个副本的在不同视图中提交。此外,这个不变式保证了任何正常节点的本地确认最终会确认f+1
个更多的正常副本。
结束
每个副本执行committed-local(m, v, n, i)
后所请求的操作,并且 i 的状态按照提供安全属性所需的相同顺序执行请求。在执行那里操作后,副本发送一个答复给客户端。副本放弃其时间戳低于发送给客户端的最后一个回复中的时间戳的请求,以保证一次的语义。
我们不依赖于有序的消息传递,因此副本可能无序地提交请求。这并不重要,因为它保持预准备,准备和提交记录的消息,直到相应的请求可以被执行。
图1显示了在主节点没有故障的正常情况下算法的操作。副本0是主节点,副本3是有缺陷的,C是客户端。
4.3 垃圾收集
本节讨论用于从日志中丢弃消息的机制。为了节省内存,系统需要一种将日志中的无异议消息记录删除的机制。为了保证系统的安全性,副本节点在删除自己的消息日志前,需要确保至少f+1个正常副本节点执行了消息对应的请求,并且可以在视图变更时向其他副本节点证明。另外,如果一些副本节点错过部分消息,但是这些消息已经被所有正常副本节点删除了,这就需要通过传输部分或者全部服务状态实现该副本节点的同步。因此,副本节点同样需要证明状态的正确性。
在每一个操作执行后都生成这样的证明是非常消耗资源的。因此,证明过程只有在请求序号可以被某个常数(比如100)整除的时候才会周期性地进行。我们将这些请求执行后得到的状态称作检查点(checkpoint),并且将具有证明的检查点称作稳定检查点(stable checkpoint)。
副本节点保存了服务状态的多个逻辑拷贝,包括最新的稳定检查点,零个或者多个非稳定的检查点,以及一个当前状态。写时复制技术可以被用来减少存储额外状态拷贝的空间开销。
检查点的正确性证明的生成过程如下:当副本节点i生成一个检查点后,向其他副本节点广播检查点消息``,这里n是最近一个影响状态的请求序号,d是状态的摘要。每个副本节点都默默地在各自的日志中收集并记录其他节点发过来的检查点消息,直到收到来自2f+1个不同副本节点的具有相同序号n和摘要d的检查点消息。这2f+1个消息就是这个检查点的正确性证明。
具有证明的检查点成为稳定检查点,然后副本节点就可以将所有序号小于等于n的预准备、准备和确认消息从日志中删除。同时也可以将之前的检查点和检查点消息一并删除。
检查点协议可以用来更新水线(watermark)的高低值(h和H),这两个高低值限定了可以被接受的消息。水线的低值h与最近稳定检查点的序列号相同,而水线的高值H=h+k,k需要足够大才能使副本不至于为了等待稳定检查点而停顿。加入检查点每100个请求产生一次,k的取值可以是200。
4.4 视图变化(leader change)
使用计时器的超时机制触发视图变更事件
视图变更协议在主节点失效的时候仍然保证系统的活性。视图变更可以由超时触发,以防止备份节点无期限地等待请求的执行。备份节点等待一个请求,就是该节点接收到一个有效请求,但是还没有执行它。当备份节点接收到一个请求但是计时器还未运行,那么它就启动计时器;当它不再等待请求的执行就把计时器停止,但是当它等待其他请求执行的时候再次情动计时器。
视图更改协议通过允许系统在主服务器失败时取得进展而提供活力。视图更改由超时触发,超时可防止备份无限期等待请求执行。如果备份接收到有效的请求,则备份正在等待请求5而且还没有执行。当备份接收到请求且计时器尚未运行时,它将启动计时器。当计时器不再等待执行请求时,它会停止计时器,但如果此时计时器正在等待执行其他请求,则重新启动计时器。
如果视图中的备份计时器到期,备份将启动一个视图更改以将系统移动到视图 1。它停止接受消息(除了检查点、视图更改和新视图消息)和 将 VIEW-CHANGE1 消息多播到所有副本。这是最后一个的序列号已知的 stable 检查点,是一组 21 个有效的检查点消息,用于证明正确性,并且是一组包含每个请求的集合,准备的序列号高于。每个集合包含一个有效的预准备消息(没有相应的客户端消息)和 2 个匹配的有效准备消息,这些消息由不同的备份使用相同的视图、序列号和。
当视图1的主要对象从其他副本接收到针对视图1的2条有效视图更改消息时,它将多播NEW-VIEW 1消息多播到所有其他副本,其中包含有效视图-主服务器收到的更改消息加上主服务器发送(或本应发送)的视图更改消息,是一组预准备消息(没有背负式请求)。计算方法如下:
主要决定最新的稳定检查点的序列号 min-s 和准备消息中的最高序列号 max-s。主服务器为视图 1 显示 min-s 和 max-s 之间的每个序列号。有两种情况:(1)在一些视图更改消息的组件中至少有一个带序列号的集合,或者(2)没有这样的集合。在第一种情况下,主要创建一个新的消息预备 1,其中是序列号的预备消息中的请求摘要,其中的最高视图编号为。在第二种情况下,它创建一个新的 pre-准备信息预备 1,其中是特殊 null 请求的摘要;null 请求像其他请求一样通过协议,但是它的执行是 no-op。(Paxos[18]使用了类似的技术来填补空白。)
接下来,主服务器将消息追加到它的日志中。如果 min-s 大于其最新的稳定检查点的序列号,主检查点还会在其日志中插入序列号为 min-s的检查点的稳定性证明,并从日志中丢弃信息,如第 4.3 节所述。然后它进入视图 1:此时它能够接受视图 1 的消息。
如果对视图 1 进行了正确的签名,备份将接受新视图消息,如果视图更改了该消息包含对于视图 1 是有效的,如果集合是正确的,它通过执行类似于主要用于创建的计算来验证该集合的正确性。然后,它将新信息添加到其日志中,正如主、多播所描述的那样,将每条消息准备到所有其他副本中,并添加这些准备然后进入视图 1。
此后,协议按照第 4.2 节所述继续进行。副本为 min-s 和 max-s 之间的消息重新执行协议,但是它们避免重新执行客户机请求(通过使用它们存储的关于发送给每个客户机的最后一个应答的信息)。
一个副本可能缺少一些请求消息或一个稳定的检查点(因为这些不是在新视图消息中发送的)它可以从另一个副本中获取丢失的信息。例如,副本可以获得检查点消息证明其正确性的一个副本丢失的检查点状态.由于这些副本中有 1 个是正确的,所以副本总是会获得或者以后获得一个经过认证的稳定检查点。我们可以通过对状态进行分区并用修改它的最后一个请求的序列号给每个分区加盖戳,从而避免发送整个检查点。为了使一个副本更新,只需要将过期的分区发送给它,而不需要发送整个检查点。
4.5 正确性
本节简要介绍了算法提供安全性和活性的证明;
详细信息可以在[4]中找到。
4.5.1 安全性
如前所述,如果所有未出错的副本都同意本地提交的请求序列号,则该算法提供了安全性。在第 4.2 节中,我们展示了如果准备好是正确的,准备是错误的复制品(包括),以及任何类似的.这意味着两个未出错的副本一致同意在同一视图中在两个副本本地提交的请求的序列号。视图更改协议确保未出错的副本也同意在不同的副本上以不同视图本地提交的请求的序列号。请求在一个没有错误的副本上本地提交,只有当提交的序列号为 true 时才能看到序列号。这意味着有一个集合 1 包含至少 1 个没有错误的副本,这样集合中的每个副本都准备好了。没有错误的复制品不会接受预先准备好的没有接收到新视图消息的视图(因为只有在那个时候它们才会进入视图)。但是,任何正确的新视图消息都包含来自62 个 21 个复制品。由于有 31 个副本,1 和 2 必须相交在至少一个副本没有错误。视图更改消息将确保在前一个视图中准备的事实被传播到后续视图中,除非新视图消息包含一个视图更改消息,其中一个稳定的检查点的序列号高于。在第一种情况下,该算法重新执行原子多播协议的三个阶段,使用相同的序列号和新的视图号。这一点很重要,因为它可以防止在以前的视图中分配序列号的任何不同的请求从未提交。在第二种情况下,新视图中的副本不会接受序列号小于的任何消息。在这两种情况下,副本都会同意使用序列号在本地提交请求。
4.5.2 活性
为了提供活性,如果副本无法执行请求,那么它们必须移动到新视图。但是,必须最大限度地延长至少有 21 个未出错的副本出现在同一视图中的时间段,并确保这段时间呈指数增长,直到执行所请求的某个操作。我们通过三种方式实现这些目标。
首先,为了避免过早启动视图更改,将视图1 的视图更改消息多播的副本等待视图 1 的 21 视图更改消息,然后在一段时间后启动其计时器到期。如果定时器在接收到有效的新视图之前过期在执行新视图中的请求之前,它会启动视图 2的视图更改,但这次它将等待 2在开始视图 3 的视图更改之前。
其次,如果一个副本从其他副本接收到一组有效的视图更改消息,这些消息的视图大于其当前视图,那么它就会为该视图中最小的视图发送一个视图更改消息,即使它的计时器还没有过期;这会防止它过迟地启动下一个视图更改。
第三,错误的复制品不能通过迫使频繁的视图更改来阻碍进展。错误的副本不能通过发送视图更改消息导致视图更改,因为视图更改只有在至少有 1 个副本发送视图更改消息,但是当它是主要的时候(通过不发送消息或发送错误的消息),它会导致视图更改。但是,因为主视图是副本,所以主视图不能超过个连续视图。这三种技术保证了活性,除非消息延迟的增长速度超过了无限期的超时时间,这在实际系统中是不可能的。
4.6 非决定论
状态机副本必须是确定性的,但许多服务涉及某种形式的非确定性。例如,NFS 中的 timelast-modified 是通过读取服务器的本地时钟来设置的;如果这是在每个副本上独立完成的,那么未出错的副本的状态就会发生分化。因此,需要某种机制来确保所有副本选择相同的值。通常,客户端不能选择值,因为它没有足够的信息;例如,它不知道相对于其他客户端的并发请求,它的请求将如何排序。相反,主服务器需要独立选择值,或者根据备份提供的值选择值。
如果主服务器独立地选择非确定性值,它将该值与相关的请求连接起来,并执行三阶段协议,以确保非故障副本同意请求和值的序列号。这可以防止错误的主服务器通过向不同的副本发送不同的 val-ues 来导致副本状态分化。但是,错误的主服务器可能会向所有副本发送相同的、不正确的值。因此,复制必须能够仅基于服务状态确定值是否正确(如果不正确,则做什么)。该协议适用于大多数服务(包括 NFS),但有时副本必须参与选择值以满足服务规范。这可以通过在协议中增加一个额外的阶段来实现:主要获取由备份提出的认证值,将其中的 21 与相关的请求连接起来,并启动连接消息的三阶段协议。复制品可以选择通过确定性计算 21 值和它们的状态,例如,取中位数。在一般情况下,额外的阶段可以被优化掉。例如,如果副本需要一个与其本地时钟“足够接近”的值,那么当它们的时钟在某个增量内同步时,就可以避免额外的相位。
5 优化
本节描述在正常情况下提高算法性能的一些优化。所有的优化都保持了活性和安全性。
5.1 减少沟通
我们使用三个优化来降低通信成本。
第一种方法避免发送大量的回复。客户机请求指定一个副本来发送结果;所有其他副本只发送包含结果摘要的答复。这些摘要允许客户端检查结果的正确性,同时减少网络7对于大型应答,带宽消耗和 CPU 开销显著增加。如果客户端没有从指定的副本收到正确的结果,它会像往常一样重新发送请求,请求所有副本发送完整的答复。
第二个优化将操作调用的消息延迟数量从 5减少到 4。当请求的预备谓词保持不变时,副本就暂时执行请求,它们的状态反映了所有序号较低的请求的执行情况,并且这些请求都已经提交。在执行请求之后,副本向客户端发送暂时的答复。客户端等待 2 比 1 的暂时回复。如果它接收到这么多请求,则保证最终提交该请求。否则,客户端将重新传输请求并等待一个非临时性的答复。如果视图发生更改,并被空请求替换,则暂时执行的请求可能中止。在这种情况下,复制将其状态还原为新视图消息中的最后一个稳定检查点或最后一个检查点状态(取决于哪个具有更高的序列号)。
第三个优化提高了不修改服务状态的只读操作的性能。客户机向所有副本多播一个只读请求。在检查请求是否经过适当的身份验证、客户端是否具有访问权限以及请求实际上是只读的之后,副本会立即以暂时状态执行请求。它们只在暂定状态中反映的所有请求都已提交之后才发送应答;这是防止客户端观察未提交状态所必需的。客户端等待来自不同副本的 21 条回复,并得到相同的结果。如果对数据的并发写操作影响到结果,客户机可能无法收集 21 这样的答复;在这种情况下,在重新传输计时器过期后,它将请求作为一个常规读写请求重新传输。
5.2 密码学
在第 4 节中,我们描述了一种使用数字签名对所有消息进行身份验证的算法。然而,我们实际上只将数字签名用于视图更改和新视图消息(这些消息很少发送),并使用消息身份验证码(MACs)对所有其他消息进行身份验证。这消除了以前系统中的主要性能瓶颈[29,22]。
然而,mac 对于数字签名有一个根本性的限制,即无法证明电文对于第三方是真实的。第4 节和之前的拜占庭容错算法[31,16]中用于状态机复制的算法依赖于数字签名的额外能力。我们修改了我们的算法,以便利用特定的不变量,例如,在两个没有错误的复制品上,没有两个不同的请求用相同的视图和序列数据准备的不变量。文献[5]中描述了改进的算法。在这里,我们勾勒出使用 mac 的主要含义。
Mac 的计算速度比数字签名快 3 个数量级。例如,一个 200mhzPentiumPro 需要 43ms 来生成 MD5 摘要的 1024 位模 RSA 签名,0.6ms 来验证签名[37],而在我们的实现中,在同一硬件上计算 64 字节消息的 MAC 只需要 10.3s。其他公钥密码体制生成签名速度较快,如椭圆曲线公钥密码体制,但签名验证速度较慢[37],在我们的算法中,每个签名都要经过多次验证。每个节点(包括活动客户端)与每个副本共享一个 16 字节的秘密会话密钥。我们通过将MD5 应用于消息与秘密密钥的串联来计算消息身份验证码。与使用最终 MD5 摘要中的 16 个字节不同,我们只使用 10 个最低有效字节。这种截断具有明显的优势,减少了 mac 的大小,也提高了它们对某些攻击的适应能力[27]。这是秘密后缀方法[36]的一个变体,只要 MD5 具有防冲突能力[27,8],它就是安全的。
应答消息中的数字签名被一个 MAC 所取代,这就足够了,因为这些消息只有一个目标接收者。所有其他消息(包括客户端请求,但不包括视图更改)中的签名都被我们称为身份验证器的MACs 向量替换。一个身份验证器对发送方以外的每个副本都有一个条目;每个条目都是用发送方共享的密钥和对应于该条目的副本计算的MAC。
验证一个身份验证器的时间是固定的,但是生成一个身份验证器的时间随着副本的数量线性增长。这不是一个问题,因为我们不期望有大量的副本,并有一个巨大的性能差距之间的MAC 和数字签名计算。此外,我们高效地计算认证器,MD5 被应用到消息中一次,结果上
下文通过将 MD5 应用到相应的会话密钥来计算每个向量条目。例如,在一个有 37 个副本的系统中(也就是说,一个可以容忍 12 个同时发生的错误的系统),一个认证数量级仍然可以比1024 位模 RSA 签名快得多。
身份验证器的大小随着副本数量线性增长,但增长缓慢:等于30*[(n-1)/3]bytes. 对于n <= 13(即,最多可容忍4个同时发生的错误的系统),身份验证器比具有1024位模数的RSA签名小(我们希望在大多数配置中都是如此)。
6 实现
本节描述我们的实现。首先,我们讨论复制库,它可以用作任何复制服务的基础。在 6.2 节中,我们描述了如何在复制库之上实现一个复制的NFS。然后我们描述了如何维护检查点和有效地计算检查点摘要。
6.1 复制库
复制库的客户机接口由一个过程组成,该过程使用一个参数调用一个包含调用状态机操作请求的输入缓冲区。调用过程使用我们的协议在副本上执行请求的操作,并从各个副本的应答中选择正确的应答。它返回一个指向包含操作结果的缓冲区的指针。
在服务器端,复制代码对应用程序的服务器部分必须实现的过程进行大量上调。执行请求(执行)、维护服务状态的检查点(设置检查点、删除检查点)、获取指定检查点的摘要(获取摘要)和获取丢失的信息(获取检查点、设置检查点)。执行过程接收包含请求操作的缓冲区作为输入,执行操作,并将结果放置到输出缓冲区中。其他程序将在第 6.3 及 6.4 节进一步讨论。
采用 UDP 协议实现节点之间的点对点通信,利用 UDP 协议在 IP 组播[7]上实现对复制组的组播。每个服务都有一个单独的 IP 多播组,该组包含所有的副本。这些通信协议是不可靠的;它们可能重复或丢失消息,或者无序地传递消息。
该算法容忍无序传递,拒绝重复。可以使用视图更改从丢失的消息中恢复,但这样做代价高昂,因此执行重新传输非常重要。在正常操作中,由接收方从丢失的消息中恢复消息:备份在消息过期时向主服务器发送否定确认消息,主服务器在长时间超时后重新发送预先准备的消息。对否定确认的回复可能包括稳定检查点的一部分和丢失的消息。在视图更改期间,副本重新传输视图更改消息,直到它们收到匹配的新视图消息或者转移到以后的视图。
复制库不实现视图目前的更改或重新传输。这不会影响第 7 节中给出的结果的准确性,因为算法的其余部分是完全实现(包括触发视图更改的计时器的操作),因为我们已经形式化了完整的算法,并证明了它的正确性[4]。
6.2BFS:一个拜占庭式的容错文件系统
我们使用复制库实现了 BFS,这是一个错综复杂的容错 NFS 服务。图 2 显示了 BFS 的体系结构。我们选择不修改内核 NFS 客户机和服务器,因为我们没有数字 Unix 内核的源代码。
容错 NFS 服务导出的文件系统与任何常规的NFS 文件系统一样挂载在客户机上。应用程序进程未经修改地运行,并通过内核中的 NFS 客户机与挂载的文件系统交互。我们依靠用户级中继进程来协调标准 NFS 客户机和副本之间的通信。中继接收 NFS 协议请求,调用复制库的调用过程,并将结果发送回 NFS 客户机。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PsVVP0Jc-1587040651461)(https://hongery.github.io/images/bft/pbft2.png)]
图 2:复制文件系统架构。
每个副本都使用复制库和 NFSV2 守护进程运行一个用户级进程,我们将其称为 snfsd(简单的 nfsd)。复制库接收来自中继的请求,通过向上调用与 snfsd 交互,并将 NFS 应答打包到发送给中继的复制协议中。
我们使用一个固定大小的内存映射文件来实现 snfsd。所有的文件系统数据结构,例如,inodes、blocks 和它们的空闲列表,都在映射文件中。我们依靠操作系统来管理内存映射文件/服务器页面的缓存,并异步地将修改后的页面写入磁盘。当前的实现使用 8KB 的块,inodes包含 NFS 状态信息和 256 字节的数据,用于在目录中存储目录条目,在文件中存储指向块的指针,以及在符号链接中存储文本。目录和文件也可以以类似于 Unix 的方式使用间接块。
我们的实现确保了所有的状态机9副本以相同的初始状态开始并且是确定性的,这是使用我们的协议实现的服务正确性的必要条件。主服务器提出 time-last-modified 和 timelast-accessed 的值,副本选择建议值中较大的一个,并选择一个大于为前面的请求选择的所有值的最大值的值。我们不需要同步写来实现NFSV2 协议语义,因为 BFS 通过复制[20]实现了修改后数据和元数据的稳定性。
6.3 维护检查点
本节描述 snfsd 如何维护文件系统状态的检查点。回想一下,每个副本都维护状态的几个逻辑副本:当前状态、一些尚不稳定的检查点和最后一个稳定检查点。
Snfsd 直接在内存映射文件中执行文件系统操作,以保留局部性,并使用即写即复制来减少与维护检查点相关的空间和时间开销。Snfsd为内存映射文件中的每个 512 字节块维护一个“ 写 对 拷 贝 ” 位 。 当 复 制 代 码 调 用makecheckpointupcall 时,snfsd 设置所有的写入式复制位并创建一个(volatile)检查点记录,其中包含当前序列号(作为 upcall 的参数接收该序列号)和一个块列表。此列表包含自检查点取出以来修改的块的副本,因此最初为空。记录还包含当前状态的摘要;我们将在 6.4 节中讨论如何计算摘要。
当执行客户端请求时修改内存映射文件的一个块时,snfsd 检查该块的“入拷贝”位,如果设置了该位,则将该块的当前内容及其标识符存储在最后一个检查点的检查点记录中。然后,它用新值覆盖该块,并重置其“写入时复制”位。Snfsd 会保留一条检查点记录,直到通过删除检查点 upcall(后来的检查点变得稳定时,由复制代码进行)告知丢弃它。
如果复制代码需要一个检查点来发送到另一个副本,则调用 get 检查点向上调用。为了获取块的值,snfsd 首先在稳定检查点的检查点记录中搜索块,然后搜索任何后续检查点的检查点记录。如果块不在任何检查点记录中,它将返回当前状态的值。
使用即写即拷技术以及我们最多保留两个检查点的事实,确保了保留几个逻辑状态副本的空间和时间开销很低。例如,在第 7 节描述的Andrew 基准测试实验中,平均检查点记录大小只有 182 个块,并且最多 500。
6.4 计算检查点摘要
Snfsd 作为 makecheckpointupcall 的一部分计算检查点状态的摘要。尽管检查点只是偶尔使用,但是由于状态可能很大,所以以增量方式计算状态摘要非常重要。Snfsd 使用一个增量式的抗冲突单向散列函数 AdHash[1]。这个函数将状态划分为固定大小的块,并使用其他一些散列函数(例如 MD5)计算通过将块索引与每个块的块值连接起来获得的字符串摘要。状态摘要是模块与某个大整数的摘要之和。在我们当前的实现中,我们使用了来自于即写即拷技术的512 字节块,并使用 MD5 计算它们的摘要。
为了增量地计算状态摘要,snfsd 为每个 512字节的块维护一个带有哈希值的表。将 MD5应用于块索引,并在最后一个检查点时将块值串联起来,从而获得此哈希值。当调用 make检查点时,snfsd 将获取前一个检查点状态的摘要(从关联的检查点记录)。它将 MD5 应用于与当前块值连接的块索引,从而为每个按写复制位重置的块计算新的哈希值。然后,它将新散列值添加到,从中减去旧散列值,并更新表以包含新散列值。如果修改块的数量很少,这个过程是有效的;如上所述,每个检查点平均修改182 个块。
7 工作表现评估
本节使用两个基准来评估我们系统的性能:微基准和 Andrew 基准[15]。微基准提供了对复制库性能的独立于服务的评估;它测量调用空操作(即不执行任何操作的操作)的延迟。
Andrew 基准测试用于将 BFS 与其他两个文件系统进行比较 : 一 个 是 DigitalUnix 中 的NFSV2 实现,另一个与 BFS 相同,只是没有复制。第一个比较表明,我们的系统是实用的,因为它的延迟与许多用户每天使用的商业系统的延迟相似。第二种比较使我们能够在实际服务的实现中准确地评估算法的开销。
7.1 实验装置
实验测量正常情况下的行为(即,没有视图更改),因为这是行为图 10决定了系统的性能。所有实验都是在一个客户端运行两个中继进程和四个副本的情况下进行的。四个副本可以容忍一个拜占庭错误;我们期望这个可靠性级别足以满足大多数应用程序。副本和客户端在相同的DEC3000/400Alpha 工作 站 上 运 行 。 这 些 工 作 站 拥 有133MHzAlpha21064 处理器,128MB 内存,并运行 DigitalUnix 版本 4.0。文件系统由每个副本存储在 DECRZ26 磁盘上。所有工作站均采用 10mbit/s 交 换 式 以 太 网 连 接 , 并 具 有DECLANCE 以 太 网 接 口 。 这 个 开 关 是DECEtherWORKS8t/tx。实验是在一个独立的
网络上进行的。检查点之间的间隔为 128 个请求,这导致在任何实验中都会多次发生垃圾收集。预准备消息中副本接受的最大序列号为 256 加上最后一个稳定检查点的序列号。
7.2 微基准
微基准测试测量调用空操作的延迟。它评估一个简单服务的两个实现的性能,这两个实现没有使用不同大小的参数和结果实现 null 操作的状态。第一个实现使用我们的库复制,第二个实现是取消复制并直接使用 UDP。表 1 报告了在客户端测量的只读和读写操作的响应时间。这些数据是在三次独立运行中计时 10,000 次操作调用得到的,我们报告了三次运行的中位数值。与中位数的最大偏差总是低于报告值的0.3%。我们用 a/b 表示每个操作,其中 a 和 b是操作参数的大小,结果以 KBytes 表示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aDsJN2mf-1587040651472)(https://hongery.github.io/images/bft/pbft3.png)]
表 1:微基准测试结果(以毫秒为单位);百分比开销相对于未复制的情况。
由复制库引入的开销是由于额外的计算和通信。对于考试组,读写 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 操作的开销更高,因为大消息(请求)遍历网络两次,增加了处理请求和预准备消息的加密开销。
值得注意的是,这个微基准代表了我们的算法的最坏情况开销,因为操作不执行任何工作,未复制的服务器提供了非常弱的保证。大多数服务将需要更强的保证,例如,认证连接,并且相对于实现这些保证的服务器,我们的算法引入的开销将更低。例如,相对于使用 MACs进行身份验证的未复制服务版本,复制库的开销对于读写 0/0 操作仅为 243%,对于只读 4/0操作仅为 4%。
相对于 Rampart[30],我们可以估计我们的算法所提供的性能增益的一个粗略的下界。Reiter报告说,对于一个空消息的多 rpc,Rampart 在4SparcStation10s[30]的 10mbit/s 以太网中的延迟为 45ms。多 rpc 足以让主服务器调用状态机操作,但是对于任意客户机调用操作来说,需要添加额外的消息延迟和 RSA 签名及验证来对客户机进行身份验证;这将导致至少 65 毫秒的延迟(使用[29]中报告的 RSA 计时)即使我们将这 个 延 迟 除 以 1.7 , DEC3000/400 和SparcStation10 的 SPECint92 等级的比率,我们的算法仍然会将调用读写和只读 0/0 操作的延迟分别减少 10 和 20 以上的因子。请注意,这种伸缩是保守的,因为网络占 Rampart 的延迟[29]的很大一部分,而 Rampart 的结果是使用300 位元模 RSA 签名获得的,这种签名在今天被认为是不安全的,除非用于图 11产生他们是非常频繁刷新。Securitying[16]没有公布性能数字,但是它会比 Rampart 慢,因为它的算法在关键路径上有更多的消息延迟和签名操作。
7.3 安德鲁·基准
Andrew 基准[15]模拟了软件开发工作负载。它分为五个阶段:
(1)递归创建子目录;
(2)复制源树;
(3)检查树中所有文件的状态而不检查它们的数据;
(4)检查所有文件中的每个字节的数据;
(5)编译和链接文件。
我们使用 Andrew 基准来比较 BFS 和其他两种文件系统配置:NFS-std,它是 DigitalUnix 中 的 NFSV2 实现,和 BFS-nr,它与 BFS 相同,但没有复制。Bfs-nr 在客户机上运行两个简单的 UDP 中继,在服务器上运行一个与一个版本的 snfsd 相链接的薄贴板,从中删除所有检查点管理代码。此配置在回答客户端之前不会将修改后的文件系统状态写入磁盘。因此,它不实现 NFSV2 协议语义,而 BFS 和 NFSstd 都实现了。
在 NFSV2 协议中的 18 个操作中,只有getattr 是只读的,因为文件和目录的上次访问时间属性是由原本是只读的操作设置的,例如读和查找。结果是我们对只读操作的优化很少被使用。为了说明这种优化的影响,我们还在BFS 的第二个版本上运行了 Andrew 基准测试,该版本将查找操作修改为只读。这种修改违反了严格的 Unix 文件系统语义学,但在实践中不太可能产生不利影响。
对于所有配置,实际的基准测试代码在客户机工作站上运行,使用 DigitalUnix 内核中的标准 NFS 客户机实现,使用相同的 mount 选项。基准测试的这些选项中最相关的是:UDP 传输、4096 字节读写缓冲区、允许异步客户机写入和允许属性缓存。
我们报告每个配置基准测试 10 次运行的平均值。在运行基准的总时间内,样本标准差总是低于报告值的 2.6%,但在前四个阶段的个别时间,这一比例高达 14%。这种高变异也存在于NFS-std 配置中。报告平均值的估计误差在个别阶段低于 4.5%,在总数中低于 0.8%。
表 2 显示了 BFS 和 BFS-nr 的结果。Bfs-strict和 BFS-nr 的比较表明,这个服务的拜占庭将军问题开销很低ーBFS-strict 只需要多 26%的运行时间
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4FteVXlX-1587040651477)(https://hongery.github.io/images/bft/pbft4.png)]
表 2:Andrewbenchmark:BFS vs BFS-nr。时间以秒计算。
完整的基准。这种开销低于微基准测试的销,因为客户机在两次操作之间,即在接收对一个操作的应答和发出下一个请求之间,以及在服务器上执行一些计算之间,花费了相当一部分运行时间。但是基准测试阶段的开销并不一致。
主要原因是客户端在两个操作之间花费的计算时间不同;前两个阶段的相对开销较高,因为客户端在两个操作之间花费的计算时间约占总时间的 40%,而在后三个阶段花费的时间约占70%。
结果表明,采用只读优化方法进行查找可以显著提高 BFS 的性能,并且相对于 BFS-nr 降低了 20%的开销。这种优化在前四个阶段具有重大影响,因为在 BFS-strict 中等待查找操作完成所花费的时间至少是这些阶段所用时间的20%,而在最后一个阶段所用时间不到 5%。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DWy2R6XY-1587040651482)(https://hongery.github.io/images/bft/pbft5.png)]
表 3:Andrew 基准:BFSvsNFS-std。时间以秒计算。
表 3 显示了 BFS 和 NFS-std 的结果。这些结果表明,BFS 可以在实际应用中得到应用ーーBFS 严格只需要 3%的时间运行完整的基准测试。因此,可以用 BFS 替代 DigitalUnix 中的NFSV2 实现,而不会影响用户感知的延迟。此外,对查找操作进行只读优化的 BFS 实际上比NFS-std 快 2%。
Bfs 相对于 NFS-std 的开销不是图 12所有阶段都一样。两个版本的 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]) 。 Viewstamped 复 制 [26] 和Paxos[18]使用带有主数据和备份的视图来容忍异步系统中的良性错误。容忍拜占庭式错误需要一个更复杂的协议,其中包括密码认证、额外的预备阶段以及触发视图更改和选择初选的不同技术。此外,我们的系统使用视图更改只选择一个新的主视图,而不选择一组不同的副本来形成新视图,如[26,18]所示。一些协议和一致性算法容忍异步系统中的拜占庭式错误(例如,[2,3,24])。然而,它们并没有为状态机的复制提供一个完整的解决方案,而且,它们中的大多数都是为了证明理论上的可行性而设计的,并且在实际应用中速度太慢。在正常情况下,我们的算法类似于文献[2]中的拜占庭式协议算法,但是该算法无法经受主要故障。
与我们的工作关系最密切的两个系统是Rampart[29,30,31,22]和 securitying[16]。它们实现了状态机复制,但是比我们的系统慢了不止一个数量级,而且最重要的是,它们依赖于同步假设。
Rampart 和 securityering 都必须从组中排除错误的副本以取得进展(例如,删除错误的主要副本并选择新的主要副本),并执行垃圾收集。他们依靠故障检测器来确定哪些副本是故障的。然而,故障检测器在异步系统中不能准确,也就是说,它们可能将副本错误地归类为故障。由于正确性要求少于 13 个组成员是错误的,因此错误分类可能会通过从组中删除非错误的副本而影响正确性。这就打开了一条攻击途径:攻击者获得了对单个副本的控制权,但是并没有以任何可察觉的方式改变其行为;然后它会正确地减速复制品或者他们之间的联系,直到足够多的复制品被排除在组织之外。
为了降低错误分类的概率,故障检测器可以校准延迟分类副本为错误。然而,对于可以忽略不计的概率来说,延迟必须非常大,这是不可取的。例如,如果主服务器实际上已经失败,那么组将无法处理客户端请求,直到延迟到期。我们的算法不容易受到这个问题的影响,因为它从来不需要从组中排除副本。Phalanx[23,25] 应 用 quorum-replicationtechniques[12]在异步系统中实现拜占庭式容错。此工作不提供通用状态机复制;相反,它提供了一个数据存储保存库,其中包含读写各个变量和获取锁的操作。它为读写操作提供的语义比我们的算法提供的语义更弱;我们可以实现任意操作来处理任意数量的变量,而在密集阵中,需要获取和释放锁来执行这些操作。目前还没有关于 Phalanx 的性能数据,但是我们相信我们的算法更快,因为它在关键路径上有更少的消息延迟,而且因为我们使用的是 MACs 而不是公钥密码。Phalanx 中的方法提供了改进可伸缩性的可能性;每个操作只由一个副本子集处理。但是,这种实现 scala 可靠性的方法代价高昂:它需要 41 来容忍错误;每个副本需要一个状态副本;每个副本上的负载随着(它是 o1)缓慢下降。
9 结论
本文描述了一种新的状态机复制算法,该算法能够容忍拜占庭式错误,并且能够在实际中使用:它是第一个能够在像 Internet 这样的异步系统中正确工作的算法,它提高了以前算法的性能超过一百万数量级。
论文还描述了 BFS,一种具有拜占庭容错能力的 NFS 实现。Bfs 演示了使用我们的算法实现接近未复制服务性能的真实服务是可能的ーBFS 的性能只比 DigitalUnix 中标准 NFS 实现的性能差 3%。这种良好的性能归功于一些重要的优化,包括用消息认证码的向量取代公钥签名,减少消息的大小和数量,以及增量检查点管理技术。
拜占庭容错算法之所以在未来非常重要的一个原因是,它们可以允许系统在出现软件错误时继续正确工作。并非所有错误都是可以避免的;我们的方法不能掩盖发生的软件错误图 13所有的复制品。然而,它可以掩盖在不同副本上独立发生的错误,包括不确定的软件错误,这是最有问题的和最持久的错误,因为它们是最难检测的。事实上,我们在运行系统时遇到了这样的软件 bug,尽管如此,我们的算法仍然能够正确地继续运行。
在改进我们的制度方面还有许多工作要做。一个特别有趣的问题是减少实现我们的算法所需的资源量。只有当某个完整的复制品失败时,使用复制品作为协议中涉及的目击者,才能减少复制品的数量。我们还认为,可以将该状态的副本数量减少到 1,但具体细节仍有待确定。