万字长文带你透彻理解分布式高可用算法可靠广播

可靠广播

顾名思义,广播就是一个进程发送消息并让所有进程接收消息的过程。

我们把发送消息的进程称为广播进程,接收消息的进程称为接收进程。

本章将介绍多种广播抽象。每个链路抽象都有两个接口事件,一个是输入事件<Broadcast|m>,它表示广播消息 m;另一个是输出事件<Receive|p,m>,表示接收广播进程为p的消息m。

由于在下面的介绍中会大量的出现基于某个广播实例a实现另一个广播实例b的情况,为了区分不同广播实例的广播和接收行为,我们可以说“通过实例 xxx[广播/接收]消息 m”。如果不指明实例变量,仅仅说“[广播/接收]消息 m”,则表示当前抽象、算法所描述的实例。例如,在介绍正则可靠广播的实现算法中,“[广播/接收]消息m”是指“正则可靠广播实例[广播/接收]消息m”。

尽力广播

尽力广播是一种较弱的可靠广播,它仅依靠消息的广播进程来提供可靠性的广播。如果消息的广播进程一直是正确的,那么消息最终会被其他正确的进程接收。其他的进程只是静静地等待消息,不对广播的可靠性做任何贡献。

定义

抽象 6-1定义了尽力广播的接口和特性。

尽力广播定义了两个接口事件,一个是输入事件<Broadcast|m>,表示广播消息m;另一个是输出事件<Receive|p,m>,表示接收进程 p 广播的消息 m。尽力广播有三个特性,其中,BEB1有效特性是指,如果消息m的广播进程是正确的,那么所有正确的进程(包括广播进程本身)都将接收消息m;如果消息m的广播进程失败了,例如崩溃了,则不能保证每个正确的进程都会接收消息m。

静音型失败算法

算法 6-1实现了尽力广播实例beb,适合于静音型失败模型,韧性小于N。它的实现非常简单,即利用可靠链路作为进程间点到点的链路,每当尽力广播实例beb广播消息m时,就会通过可靠链路实例rl将消息m依次发送给接收进程。当可靠链路实例rl接收消息m时,实例beb就接收消息m。只要系统中还剩下一个正确的进程,该算法就仍然满足抽象 6-1的要求,因此该算法的韧性小于N。

正确性证明

(1)证明BEB1有效特性。该算法使用可靠链路,根据可靠链路的RL1可靠接收特性可知,如果广播进程 p 是正确的,那么所有正确的进程最终都会通过实例 rl接收消息m,进而通过实例beb接收消息m。得证。

(2)证明BEB2不重复特性和BEB3不创造特性。通过可靠链路的RL2不重复特性和RL3不创造特性得证。

性能分析

每广播一个消息,需要通信次数1次,发送的消息数为N。

正则可靠广播

尽力广播仅确保在广播进程正确的情况下,消息会被所有正确的进程接收。但如果广播进程失败了,例如崩溃了,由于底层的可靠链路并不保证消息会被发送给每个正确的进程,因此尽力广播不能确保当广播进程失败时,系统中每个正确的进程都会接收消息。换句话说,尽力广播并不能保证系统中所有的进程在接收消息这件事情上达成一致。

而正则可靠广播可以部分解决这个问题。它能够确保,只要有一个正确的进程接收了该消息,那么系统中所有正确的进程将接收该消息。换句话说,系统中所有正确的进程在接收消息这个事情上达成了一致,要么都接收,要么都不接收。

定义

抽象 6-2定义了正则可靠广播的接口和特性。

正则可靠广播定义了两个接口事件,一个是输入事件<Broadcast|m>,表示广播消息 m;另一个是输出事件<Receive|p,m>,表示接收到进程 p广播的消息 m。正则可靠广播中的RB4一致特性是指,只要有一个正确的进程接收了消息m,则所有正确的进程都会接收消息m,即使广播进程失败了,也是如此。

停止型失败算法

算法 6-2 实现了正则可靠广播实例 rb,适用于停止型失败模型,韧性小于 N。它的思路是:在正常情况下,当进程p通过正则可靠广播实例rb广播消息m时,实例rb通过尽力广播实例beb广播消息m。考虑到可能的进程失败,进程p利用完美失败检测实例 P 来维系一个正确的进程的集合 correct,一旦检测到失败的进程,就通过实例beb重复广播该失败的进程曾广播或正在广播的消息。此外,当实例beb接收消息m时,如果消息m的广播进程不属于集合correct,则说明该广播进程已经失败了,于是通过实例beb重复广播该消息m。进程p重复广播消息m的行为,实际上是利用了进程p来确保其他正确的进程接收消息m。

正确性证明

(1)证明RB1有效特性。通过尽力广播的BEB1有效特性得证。

(2)证明RB2不重复特性。由于实例rb接收消息m的前提条件是消息m不在集合from中,而且接收消息m后必然将消息m放入集合from中,因此实例rb不会重复接收消息m。

(3)证明RB3不创造特性。通过可靠链路的RL3不创造特性得证。

(4)证明RB4特性。假设消息m的广播进程s是正确的,那么根据尽力广播的BEB1有效特性可知,所有正确的进程最终将通过尽力广播实例beb接收消息m,进而通过正则广播实例rb接收消息m。假设消息m的广播进程s是失败的,且正确的进程p通过实例rb接收了消息m,那么有两种情况:一是如果进程p已经检测到进程s的失败,那么条件s∉correct成立,则通过实例beb重复广播消息m;二是如果进程p尚未检测到进程s的失败,但根据完美失败检测的PFD1强完成特性可知,实例P最终将宣告输出事件<Crash>,从而将通过实例beb重复广播消息m。又根据尽力广播的BEB1有效特性可知,所有正确的进程最终将通过beb实例接收消息m,进而通过实例rb接收消息m。得证。

性能分析

如果消息的广播进程没有失败,则每个广播操作所需要的通信次数为1,消息个数为N。如果消息的广播进程在其他进程都已经接收消息之后失败了,而其他进程未失败,则每个广播操作需要的通信次数仍然为1,消息个数的复杂度为O(N 2)。对于进程依次失败的极端情况,通信次数的复杂度为O(N),消息个数的复杂度为O(N 2)。

静音型失败算法

算法 6-2 是面向停止型失败模型的,用到了完美失败检测,因此仅适用于同步系统。为了在异步系统中也能实现正则可靠广播抽象,算法 6-3 实现了正则可靠广播实例rb,适用于静音型失败模型,韧性小于N。它的思路是:

当实例rb通过尽力广播实例beb接收到新消息时,实例rb不仅接收该消息,还会再次通过实例beb将该消息二次广播出去。

尽管该算法适用于异步系统,但韧性小于N。我们可以看到,很多适用于异步系统的算法都只能做到韧性小于N/2。

正确性证明

(1)证明RB1有效特性。通过尽力广播的BEB1有效特性得证。

(2)证明RB2不重复特性。由于实例rb接收消息m的前提条件是消息m不在集合received中,而且接收消息m后必然将消息m放入集合received中,因此实例rb不会重复接收消息m。得证。

(3)证明RB3不创造特性。通过可靠链路的RL3不创造特性得证。

(4)证明RB4一致特性。由于任何正确的进程的实例rb在接收某个消息m后必然会通过尽力广播beb实例进行重复广播,又根据尽力广播的BEB1有效特性可知,所有正确的进程的实例rb都将接收消息m。得证。

性能分析

如果没有进程失败,则每个广播操作所需要的通信次数的复杂度为1,消息个数的复杂度为O(N 2)。如果消息的广播进程在其他进程都已经接收消息之后失败了,但其他进程未失败,则每个广播操作需要的通信次数仍然为1,消息个数的复杂度为O(N 2)。对于进程依次失败的极端情况,通信次数的复杂度为O(N),消息个数的复杂度为O(N 2)。

统一可靠广播

正则可靠广播能够保证:如果一个正确的进程接收了消息m,则所有正确的进程都将接收消息m;但不能保证,如果一个进程在接收消息m之后就失败了,例如崩溃了,其他所有正确的进程也会接收消息m。

而统一可靠广播解决了这个问题,它保证:即使一个进程失败了,但它在失败前曾经接收了消息m,那么所有正确的进程都将接收消息m。因此,统一可靠广播比正则可靠广播具有更高的可靠性。

统一可靠广播所提供的更强的保证是有实际意义的。例如,在对一致性要求很高的分布式系统中,某个进程尽管失败了,但是在失败之前已经接收了消息并进行了相关处理,而如果其他进程都没有接收该消息,就可能会出现不一致的情况。

定义

抽象 6-3定义了统一可靠广播的接口和特性。

统一可靠广播中的URB4统一一致特性是指,只要有一个进程p接收了消息m,即使进程p在接收消息m后失败了,所有正确的进程也都会接收消息m。

停止型失败算法

实现正则可靠广播的算法 6-2 和算法 6-3 之所以不能保证失败的进程在接收消息后,所有正确的进程都会接收该消息,其原因是先接收消息、后重复广播,因此只要进程在接收消息之后、重复广播之前崩溃,其他进程就不能接收到该消息。

算法 6-4实现了统一可靠广播实例urb,适用于停止型失败模型,韧性小于N。该算法的思路是,仅当所有其他正确的进程接收到确认消息之后,实例urb才算接收了消息,这样就能确保只要某个进程的实例urb接收了这个消息,系统中每个正确的进程的实例urb最终都能接收该消息。具体算法如下。

实例urb利用完美失败检测实例P来检测失败的进程,以维护一个正确的进程集合correct。当实例urb广播消息m时,就通过尽力广播实例beb广播消息m给所有的进程。当实例beb接收一个消息m时,就通过实例beb重复广播该消息m,即确认消息。当然,实例urb利用一个集合pending来记录那些已经通过调用beb广播过的消息,以避免对同一消息重复广播。

实例 urb 还利用一个集合 ack[m]来记录实例beb接收了哪些进程广播的确认消息m。当且仅当实例beb接收了所有正确的进程广播的消息m,即correct⊆ack[m]时,实例urb才接收消息m。这样就确保了只要本进程的实例urb接收了消息m,则所有正确的进程的实例urb最终都会接收消息m。实例urb利用一个集合received来记录已经接收的消息,以避免重复接收。

正确性证明

通过尽力广播的BEB1-BEB3特性分别可证URB1-URB3特性。

(1)证明URB1有效特性。通过尽力广播的BEB1有效特性得证。

(2)证明URB2不重复特性。由于实例urb在接收消息m的前提条件是消息m不在集合received中,而且接收消息m后必然将消息m放入集合received中,因此实例urb不会重复接收消息m。得证。

(3)证明RB3不创造特性。通过可靠链路的RL3不创造特性得证。

(4)证明URB4统一一致特性。

根据完美失败检测的PFD1强完成特性和PDF2强准确特性可知,correct集合最终将包含且仅包含所有正确的进程。

假设某个正确的进程p的实例urb接收了某个消息m,那么canreceive(m)必然为真,即correct⊆ack[m]也必然为真,这意味着进程p的实例beb接收了所有正确的进程广播的消息m。

不失一般性,假设进程p的实例beb接收了某个进程q广播的消息m。尽力广播的BEB1有效特性保证,只要进程q是正确的,则所有正确的进程的实例beb必然会接收进程q广播的消息m。而在这些进程中,除进程p以外,假设还有另一个进程r通过实例beb接收了进程q广播的消息m,那么进程r也将通过实例beb重复广播消息m给进程q,因此只要进程r和进程q都是正确的,进程q的实例beb就必然最终接收诸如进程r这样的所有正确的进程广播的消息m,从而进程q的实例urb也必然接收消息m。这也就证明了只要进程p的实例urb接收了消息m,那么进程q的实例urb也会接收消息m。得证。

性能分析

在理想情况下,所有的进程的实例beb都在同一时刻接收消息m,且在同一时刻通过实例beb重复广播消息m,又在同一时刻通过实例beb接收上述重复广播的消息m,且在同一时刻实例urb接收消息,则所需要的通信次数为2,消息个数的复杂度为 O(N 2)。在最坏情况下,进程依次失败,通信次数为 N+1,消息个数的复杂度为O(N 2)。

静音型失败算法

算法 6-4 依赖完美失败检测,仅适用于同步系统。而算法 6-5 也实现了统一可靠广播实例urb,但适用于静音型失败模型,也可适用于异步系统,不依赖任何失败检测抽象,只要确保正确的进程能够构成一个多数派,即f<N/2即可。

在该算法中,“function canreceive (m) returns Boolean is”表示定义了一个返回值为布尔类型的函数canreceive(m),在该函数的实现中用到了表达式#(S),它表示集合S的大小或者数组(链表、向量)S中非⊥元素的个数。

正确性证明

(1)URB1-URB3特性的证明与算法 6-4的证明类似,故省略。

(2)证明URB4统一一致特性。

假设进程p的实例urb接收了消息m,那么进程p的实例beb必然接收了严格大于N/2个进程广播的消息m,以使得#(ack[m])>N/2成立。又因为系统中失败的进程数f<N/2,因此进程p的实例beb至少接收了一个正确的进程广播的消息m。假设这个正确的进程为 q,所有正确的进程构成的集合为 Q,则 q∈Q,这个过程就相当于进程q的实例urb针对进程集合Q广播了消息m。由于集合Q中的进程都是正确的,记集合中元素的个数为|Q|,因此集合Q中的每个进程的#(ack[m])必然等于|Q|。又因为f<N/2,因此|Q|=N-f>N/2必然成立,即#(ack[m])>N/2,因此集合Q中的进程的实例urb都将最终接收消息m。证毕。

算法分析

实际上,如果把该算法中canreceive(m)函数的判断条件改为#(ack[m])>0,那么该算法就退化为了算法 6-3,即只要实例beb接收了消息m,实例urb就接收消息m。

系统中允许失败的进程数的上限最大可以是多少?

假设系统中允许失败的进程数的上限为f,即进程失败数x必须小于等于f,记作x≤f。如果f<N/2,那么将canreceive(m)函数中的判断条件改为#(ack[m])≥N-f后,算法仍然是正确的,因为N-f>N/2,即#(ack[m])>N/2,算法已经被证明是正确的。

但如果f≥N/2,那么将canreceive(m)函数中的判断条件改为#(ack[m]) ≥N-f后,算法则不是正确的。因为f≥N/2,所以N-f≤N/2,故f≥N-f成立。

也就是说,系统中允许失败的进程数的上限(“≥”的左边)“大于等于”进程的实例urb接收消息m所需要的实例beb接收的消息 m的个数(“≥”的右边)。那么,如果某进程p的实例beb接收的消息m的个数刚好等于N-f,而这N-f个通过实例beb广播消息m的进程恰好在将消息m发送给进程p之后、尚未将消息m发送给其他任何进程之前就全部崩溃了,且进程p恰好在实例urb接收了消息m后、尚未向上述N-f个进程以外的任何进程发送消息m之前就崩溃了,那么就会导致进程p的实例urb接收了消息m,但仍有f个进程是正确的,却从未通过实例beb接收到消息m,故不会通过实例urb接收消息m,从而违反了URB4统一一致特性。

因此,系统中允许失败的进程数的上限f必然严格小于N/2。

性能分析

该算法的性能,包括通信次数和消息个数,与算法 6-4是相同的。

顽固广播

尽力广播、正则可靠广播和统一可靠广播都没有考虑进程在崩溃后恢复的情形。也就是说,如果一个进程崩溃了,即使崩溃后又被恢复了,仍被视为失败了而退出本轮算法的执行,因此不适用于恢复型失败模型——将崩溃后又恢复的进程视作正确的进程。

为了在恢复型失败模型中使用广播,需要引入顽固广播这个抽象。

定义

抽象 6-4定义了顽固广播的接口和特性。

顽固广播抽象定义了两个接口事件,一个是输入事件<Broadcast|m>,表示广播消息m;另一个是输出事件<Receive|p,m>,表示接收到广播进程为p的消息m。

顽固广播有两个特性。其中,SBEB1有效特性是指,如果消息m的发送进程从未崩溃过,那么所有的正确的进程(包括发送进程本身)都将无限次地接收消息m。换句话说,如果消息m的发送进程曾经崩溃过,尽管最终恢复了而被认为是正确的,也不能保证每个正确的进程都会无限次地接收消息m。

恢复型失败算法

算法 6-6 实现了顽固广播实例 sbeb,适用于恢复型失败模型。它的实现非常简单,即利用顽固链路实例 sl 作为进程间点到点的通信,每当受理了输入事件<Broadcast>后,就调用实例sl向系统中所有的进程发送消息。

当实例sl接收另一个进程发送的消息时,就宣告输出事件<Receive>。

正确性证明

由于该算法使用顽固链路,那么SBEB1尽力有效特性和SBEB2不创造特性可以通过顽固链路的SL1顽固接收特性和SL2不创造特性证明。

性能分析

每广播一个消息,需要通信1次,消息个数为N。如果考虑到顽固链路的重传,那么消息的个数会更多。不过,与顽固链路类似,这种重传仅限于顽固广播实例的生命周期中,当上层应用通过一个顽固广播实例完成了某个任务后,这个顽固广播实例就可以被销毁了,内部的顽固链路实例也随之被销毁了,从而避免整个系统范围内出现消息重传的现象

概率广播

前面所述的各种广播抽象都是确定性的广播,在系统规模很大时,会给单个进程造成较大的负担。例如,在一个由百万级进程组成的分布式系统中,即使采用一致性要求最低的尽力广播,广播进程也需要向一百万个进程发送消息,这对于广播进程而言是一个沉重的负担。若考虑到一致性要求更高的统一可靠广播,几乎系统中的每个进程都需要向(或从)百万级别进程发送(或接收)消息,这将给每个进程造成极大的负担。

现在考虑为一致性要求最低的尽力广播设计一个新的算法,以适应与大规模的分布式系统。直观来看,这种算法只需原始广播进程将消息发送给其他少数几个进程,然后由后者继续转发,最终使系统中的每个进程都接收到该消息。

这个过程与病毒扩散非常类似:在一个封闭的社区里,人与人之间都可能有接触。起初,只有一个人感染病毒而成为感染者。一天以后,该感染者将病毒传染给了其他k个人,而且这k个人的选择是随机的。第二天,这k个感染者中的每个人又将病毒随机地传染给另外k个人。若不考虑已在第一天被传染的人在第二天可能被重复传染的情况,那么第二天将新增 k2个感染者……

经过多天以后,这个社区里的绝大部分人都可能成为感染者,但或许有极少数人未被传染,因为每个感染者在传播病毒时是随机选择被传染对象的,因此理论上存在有人未被传染的概率。

除病毒扩散以外,消息传播、谣言传播也是类似的广播过程。不难看出,在这种广播过程中,消息接收进程的选择具有随机性,即并不能确切地保证所有进程都接收到了消息,因此这种广播称为概率广播,接收到消息m的进程也被称为被消息m感染的进程。

定义

抽象6-5定义了概率广播的接口和特性。

概率广播与尽力广播的接口和特性几乎完全一样,除了对“有效”这个特性的定义。在尽力广播中,BEB1有效特性要求接收是一个确定性事件。而在PB1概率有效特性的定义中,接收是一个概率性事件,即存在一个正数ε,使得每个正确的进程最终接收消息m的概率不小于1-ε,所以概率广播属于组合模型中的随机化模型。从极限的角度看,如果给予足够长的时间,那么每个正确的进程最终接收消息的概率应该是1。

随机化算法:尽力推送

算法 6-7 基于尽力推送思路实现了概率广播实例 pb,适用于静音型失败模型,韧性f小于进程总数N。

当实例pb广播消息m时,除立即接收消息m外,还通过调用gossip过程向其他进程发送消息 m。这个 gossip 过程并非向所有的其他进程发送消息m,而是从其他进程中随机挑选k个进程作为消息的接收进程,然后通过公平丢包链路实例fll将消息m依次发送给上述k个进程,这个过程被称为“一轮gossip过程”。该算法用变量R表示总的gossip轮次。

第一次调用gossip过程时,当前轮次r被赋值为R。每当实例fll接收消息m时,会将轮次r减1,然后再次调用gossip过程。直到轮次r不再大于1后,停止继续调用gossip过程,至此算法结束。

在该算法中,上述变量 k 被称为扇出(fanout),是算法中的关键变量。如果 k太大,则会对进程造成较大的转发负载,同时消息冗余转发的比例也会增加;如果k太小,则需要很多轮次才能满足PB1概率有效特性的要求。

该算法并不能确定性地保证每个正确的进程最终会接收消息。例如,在最后一轮gossip过程中,随机选出来的进程恰好都是原来已经接收过消息的进程,那么最后一轮gossip过程实际上并没有将消息发送给任何新的进程。

又例如,gossip过程的轮次的上限R如果太小,也会造成一部分进程从未接收过消息。

图 6-1是一个总进程数为9、扇出为3、轮次为3的概率广播示意图。可以看出,在每轮gossip过程中,每个进程无须向所有进程发送消息,而仅需要向其他3个进程发送消息,整个系统中没有负载特别大的进程。其次,在第 2轮和第 3 轮的 gossip过程中,有些进程会接收到重复消息,这说明概率广播算法存在消息重复转发的资源浪费现象。最终,在第3轮后,系统中的每个进程都接收到消息。

图6-1 概率广播示意图

正确性证明

(1)证明PB3不创造特性。通过公平丢包链路的FLL3不创造特性得证。

(2)证明PB2不重复特性。由于实例pb在接收消息之前,对消息是否重复接收进行了判断,故满足。

(3)证明PB1概率有效特性。

假设系统中总进程数为N、扇出为k。显然,k必然大于0,N必然大于k。

为了简化证明过程,假设所有的进程都是正确的。若将失败的进程纳入上述分析,只需要假设失败的进程在算法运行之初就全部失败了,上述变量 N 表示系统中正确的进程的总数即可,并不影响上述分析和证明过程。

情况一:每轮只有一个进程转发消息。

实际上,每轮gossip过程中有多个进程同时转发消息,但为了分析方便,假设每轮只有一个进程转发消息,如图 6-2所示。

图6-2 每轮gossip过程只有一个进程转发消息

在此假设中,记在第r轮gossip过程后,被感染的进程总数为Sr,那么在第r+1轮gossip过程后被感染的进程总数为

其中,

表示在第r+1轮gossip过程中,消息转发进程在从N-1个候选进程中选择 k 个进程时,这 N-1 个候选进程中有多大比例已经被感染。显然,消息转发进程本身肯定被感染了,而在第r轮gossip过程之后,共有Sr个进程被感染,因此在这N-1个候选进程中被感染的进程数为Sr-1,候选进程的被感染率为

不难看出,由于0<k<N,故0<γ<1,因此随着r变大,Sr将严格单调递

增,并无限趋近于N。经过r轮gossip过程后,每个进程被感染(接收消息)

的概率为p=

。不难得出,当r≥log1-γ

时,p≥1-ε成立,即每个进程最终接收消息的概率不小于1-ε。

情况二:每轮有多个进程同时转发消息。

真实的情况是每轮gossip有多个进程同时转发消息,如图 6-3所示。

图 6-3 每轮gossip有多个进程同时转发消息

记在第r轮gossip过程后,被感染的进程总数为Tr。显然初始状态T0=S0=1;第1轮gossip过程中,有1个进程向其他k个进程在转发消息,因此T1=S1=1+k;在第2轮gossip过程中,有k个进程同时向其他k 2个进程转发消息,因此T2应该等于串行假设中第1+k轮之后被感染的进程数S1+k;在第3轮gossip过程中,有k 2个进程同时向其他k 3个进程转发消息,因此T3应该等于串行假设中第1+k+k 2轮后被感染的进程数S1+k+k 2。以此类推,有

证毕。

性能分析

首先来看通信次数,每轮gossip过程就是一次通信,因此至少要经历Rmin次通信才能使p≥1-ε成立,通信次数的复杂度与进程总数N、扇出k及ε的取值有关。

如图 6-4所示,总进程数N=1000000、扇出k=2且多个进程同时转发消息时,仅用了r=25轮即可将消息扩散到整个系统。

图6-4 多个进程同时转发消息的扩散速度

如表6-1所示,可以看出总进程数N(行首)从1000到1015、扇出k(列首)从2 到 10、ε=10-25时对应的「Rmin㊣(表中的格子)。仅需个位数的扇出大小、十位数的通信次数,就可将消息广播到规模巨大的分布式系统中的每个进程。

表6-1 总进程数N、扇出k与「Rmin㊣的对应关系(1)

接下来看消息个数。第r轮gossip过程发送消息的个数为k r,至少需要经历Rmin轮gossip过程,因此消息个数的总数为

当k≪N、0<ε<1时,利用泰勒展开,可得

因此,当k≪N、0<ε<1时,为了使每个进程最终接收消息的概率不小于1-ε,消息个数的总数是-N lnε。

不难看出,消息个数主要取决于N和ε,与k关系不大。如果想提高广播的可靠性,就必须减小ε,而代价就是大量地重复转发消息。当ε→+0时,消息个数将趋向于+∞。

随机化算法:推拉结合

算法 6-7 描述的尽力推送算法虽然在通信次数复杂度上有优势,但是消息个数复杂度太高,主要是因为随着被感染进程的比例逐渐增加,重复转发的比例也越来越大,最后一轮gossip过程中选出来的消息接收进程中,只有极少部分是未被感染的进程。

算法 6-8 基于推拉结合的思路实现概率广播实例 pb,适用于静音型失败模型,韧性f小于进程总数N。该算法分成两个阶段,第一阶段为推送过程,第二阶段为拉取过程。推送过程使用与算法 6-7 相同的概率广播实例,用 upb 表示,只不过ε较大,例如ε=50%,即让过半的进程被感染即可,因此除最后一轮gossip过程外,其余的每轮gossip过程中,重复转发率均低于50%,这将大大降低消息个数的复杂度。

为了让其余一半左右的进程也被感染,拉取过程将发挥作用。拉取过程其实是由推送过程引发的。进程p1先通过推送过程完成对消息m1的广播,此时系统中大约有一半左右的进程被消息m1感染了,记这些被消息m1感染的进程集合为S。接下来,进程 p1又对消息 m2进行推送,此时消息 m2的接收进程的集合为 R。如果当集合 R中的某个进程p2接收到消息m2时,它的next[p1]变量为1,即表示进程p2期待从进程p1接收的下一个消息是m1。也就是说,进程p2尚未接收到消息m1就接收到了消息m2,于是通过gossip过程向其他进程发送“请求消息m1的消息”(简称请求消息),里面写着“进程p2需要消息m1”。假设请求消息被发送给了进程p3,对于进程p3而言:

(1)如果恰好持有消息m1,就会把消息m1发送给进程p2;

(2)否则,就调用gossip过程继续向其他进程转发请求消息。当持有消息m1的进程(例如进程p6)接收到了进程p3转发的请求消息时,会把消息m1直接发送给进程p2,而非进程p3。

这种通过gossip过程不断地请求所缺失的消息的过程被称为拉取过程。

由于集合S的大小约为总进程数的一半,因此拉取过程中的每轮gossip过程至少有50%的概率能够把请求发送给集合 S 中的进程,因此拉取过程可以很高效地获得所缺失的消息m1。考虑到降低内存的开销,集合S中的每个进程并不一定持有消息m1,而是概率性地持有消息m1,这个消息持有概率为α。例如,当α=0.3时,每个进程有30%的概率持有消息 m1,这样就把持有消息的内存开销分摊到了多个进程上。因此拉取过程中的每轮gossip过程中有50%×30%=15%的概率能够把请求发送给集合S中恰好持有消息m1的进程。

大部分情况下,拉取过程是成功的,但万一失败,则通过定时器触发一个事件<Timeout|[JUMP,s,sn]>,例如,将next[p1]强制设为3,即跳过消息m3以前的那些久等不来的消息,这样就保证了整个算法的活性。

同时,为了防止进程长期持有老旧的消息,可以通过定期触发事件<Timeout|[GC]>对老旧的消息进行清除,以降低进程持有消息的内存开销。

值得注意的是,该算法正确执行的前提是每个消息的原始发送进程都会连续地广播消息,才能引发拉取过程。

正确性证明

(1)证明 PB3 不创造特性。通过公平丢包链路的 FLL3 不创造特性及算法 6-7的不创造特性得证。

(2)证明PB2不重复特性。因为当实例pb接收消息时,对next[s]进行了自增1的操作,故对任何一个消息只可能接收一次。

(3)证明PB1概率有效特性。

要证明 PB1 概率有效特性,就是要证明,随着进程 s 不断地广播消息,每个进程被消息m1感染的概率的极限等于1。为了简化证明过程,不考虑利用超时机制跳过久等不来的消息或清除老旧消息的因素影响,并且假设所有的进程都是正确的。

假设每个广播的推送过程的ε系数为ε1,消息持有概率为α,拉取过程的k为k2。简单起见,拉取过程的gossip轮次为1。

性能分析

首先分析推送过程。由于推送过程采用的算法与算法 6-7 一样,因此复杂度的计算方法都是一样的。但由于ε系数变大,通信次数的复杂度和消息个数的复杂度都降低了。

假设把表 6-1 中的ε从10-25改为 0.5,则可以得到表 6-2,其中行首表示进程数N,列首表示扇出k,中间的格子表示将感染率降至0.5以下所需的轮次。以N=103、k=2为例,表 6-1表示需要15轮gossip过程才能将未感染率降至10-25以下,而表 6-2表示仅需要9轮gossip过程就能将未感染率降至0.5以下,后者所需要的gossip过程比前者少了15-9=6次,意味着后者转发的消息仅有前者的2 -6,即1.56%左右,消息个数的复杂度大为降低。

表6-2 总进程数N、扇出k与「Rmin㊣的对应关系(2)

然后分析拉取过程。由于每次广播消息时拉取过程的gossip轮次被假设为1,与算法 6-7 相比,在本算法的拉取过程中,进程转发请求消息的并行度较低,因此需要更多的通信次数才能满足PB1概率有效特性的要求。

接下来分析消息个数的复杂度。由于式(6-6)的pi是偏小的,式(6-3)的ci是偏大的,因此基于式(6-3)的ci计算拉取过程的消息个数的复杂度也是偏高的。如果计算出来的复杂度仍然小于算法 6-7,则说明本算法的消息个数的复杂度更低。

在发送消息m2、m3、……的拉取过程中,为拉取消息m1而产生的请求消息的总数为

根据式(6-1)可知,在发送消息m1的推送过程中,消息个数为-N lnε1。因此,让所有进程被消息m1感染所需要的消息个数的总数的上限为

根据式(6-8)可知,要达到所有进程被某个消息感染,算法 6-8 需要的消息个数(的期望值)是有上限的。换句话说,当消息个数达到这个上限时,可以认为每个进程被该消息所感染的概率等于1。这是一个很好的特性。

而算法 6-7需要的消息个数(的期望值)与ε有关,根据式(6-1)可知,当ε→+0时,消息个数将趋向于+∞。

表 6-3 对算法 6-7 和算法 6-8 所需要的消息个数进行了对比。其中,算法 6-7以ε=10-25为目标,而算法 6-8则以每个进程被感染的概率等于1(即ε=0)为目标。令两个算法的总进程数N从1000增长到1E+15、k2=2、α=0.1或0.5,算法 6-8的推送过程的ε1=0.5,拉取过程的gossip轮次为1。可以看出,消息持有概率α非常重要,只要有10%的消息被持有,算法 6-8所需要的消息个数只有算法 6-7的10%;当有50%的消息被持有时,算法 6-8需要的消息个数只有算法 6-7的2.6%。

表6-3 尽力推送和推拉结合算法关于消息个数的对

不难看出,即使当n→∞、pi→1时,消息个数的总数D仍然是有上限的,相较于算法 6-7 有明显的优势。主要的原因在于,随着 n 的增加,需要转发的请求消息越来越少,且会收敛。而在算法 6-7 中,随着轮次的增加,需要转发的消息也越来越多,而且重复转发的比例也越来越高,且不收敛。

先进先出广播

前面介绍的各种广播抽象都不涉及消息接收的顺序问题,但有时,进程需要按照消息发送的顺序或事件发生的顺序来接收消息。

例如,在一个聊天群中,进程 p1广播了消息 m1,然后又广播了消息 m2对消息m1进行评论。但是利用前面介绍的广播抽象,可能会导致消息 m2先于消息 m1被接收,聊天群中的用户看到的消息可能是乱的,因此需要确保消息按照其发送的顺序被接收,这种广播就叫作先进先出广播(First-In FirstOut Broadcast)。

定义

抽象6-6定义了先进先出广播的接口和特性。

先进先出广播的特性与正则可靠广播比较类似,只是多了FRB5特性,即保证了同一个进程广播的两个消息会按照其广播的顺序被接收。

静音型失败算法

算法 6-9实现了基于序列号的先进先出广播实例frb,可以用于静音型失败模型。实例frb对每个广播消息进行编号表示其顺序,并通过正则可靠广播实例rb进行广播。在消息的接收进程一端,当实例rb接收了一个消息m时,会检查该消息m的序号sn是否与该进程所期待的下一个序号next[s]相等。如果是,则实例frb接收该消息m;如果不是,则将该消息m保存在pending集合中,直到等到序号为next[s]的消息被实例rb接收。

正确性证明

(1)证明FRB1有效特性。假设一个正确的进程p通过实例frb广播了消息m,那么实例frb必然通过实例rb广播该消息m,且对lsn自增1。根据RB1有效特性,该进程的实例rb必然接收消息m,且消息中的sn必然等于next[self],得证。

(2)证明FRB2不重复、FRB3不创造特性。由于该算法底层使用了正则可靠广播,因此根据正则可靠广播的RB2不重复、RB3不创造特性,得证。

(3)证明FRB4一致特性。如果某正确的进程p的实例frb接收了消息m,那么进程p的实例rb必然接收了消息m。根据正则可靠广播的RB3不创造特性,则消息m必然是被其广播进程q通过实例rb广播的。根据正则可靠广播的RB4一致特性,所有正确的进程的实例rb必然接收消息m。又因为消息的序号是有序递增的,因此所有进程的实例frb迟早会接收消息m,得证。

(4)证明FRB5先进先出接收特性。由于当且仅当sn'=next[s]时,next[s]是单调递增的,所以序号小的消息一定先于序号大的消息被接收,得证。

性能分析

本算法的通信次数复杂度和消息个数复杂度均与正则可靠广播相同,具体取决于所应用的失败模型:若应用于停止型失败模型,则与算法 6-2 和算法 6-3 相同;若应用于静音型失败模型,则与算法 6-3 相同。但是每个进程需要暂存因乱序而提前被实例rb接收的消息,越“乱”则暂存得越多。

因果可靠广播

先进先出广播仅能保证同一个进程先后广播的两个消息将按照其被广播的顺序被接收进程接收,但并不能保证接收进程按照因果顺序被接收,在实际应用中会导致问题。

例如,在一个聊天群里,进程p1广播了消息m1——“你吃过了吗?”,并被进程p2接收,然后进程p2广播了消息m2对消息m1进行评论——“我吃过了。”但如果使用先进先出广播,那么进程p3可能先接收消息m2、后接收消息m1,导致进程p3看到的消息是违反因果关系的。

为了解决这个问题,我们需要一种新的广播抽象——因果可靠广播(Causual Order Reliable Broadcast),以确保进程以符合因果关系的方式接收消息的广播抽象。在上例中,因果可靠广播将保证进程p3也必须先接收消息m1、后接收消息m2。

定义

抽象6-7定义了因果可靠广播的接口和特性。

因果可靠广播的特性与正则可靠广播比较类似,只是多了CRB5特性,即保证了没有进程会违反因果顺序接收消息。

抽象6-8定义了因果统一可靠广播的接口和特性。

因果统一可靠广播与因果可靠广播唯一的区别在于,因果可靠广播中的CRB4一致特性变成因果统一可靠广播的CURB4统一一致特性,即如果进程p接收了消息m,不论进程p是否正确,所有正确的进程最终都将接收消息m。

静音型失败算法

算法 6-10实现了因果可靠广播实例crb,适用于静音型失败模型,韧性f小于进程总数N。该算法的主要思路为:每个实例crb有一个集合received,表示已经被实例crb接收的消息;还有一个链表past,表示该进程已知的因果关系。这个因果关系链表past中的每个元素是一个消息(包括消息的广播进程和消息内容本身),后面的消息不会因果先于前面的消息,整个链表past就表示该进程所掌握的因果关系。

当进程p通过实例crb广播消息m时,会把因果关系链表past也广播给所有的接收进程。当接收进程(例如进程q)接收到了消息m和因果关系(记作mpast)后,先有序遍历因果关系mpast中的消息,并接收其中从未接收过的消息,然后接收消息m。最后,将链表mpast中的消息追加到进程q本身的链表past中,把消息m也追加到进程q本身的链表past中,使得进程q学习到了进程p的因果关系。当进程q广播消息m'时,也会把进程q的因果关系past与消息m'一并对外广播,向其他进程表示“past因果先于m'”这一因果关系。

正确性证明

(1)证明CRB1有效特性。当进程p通过实例crb广播消息m时,实例crb会通过实例rb对外广播消息m。根据RB1有效特性,进程p的实例rb最终接收消息m,因此实例crb最终将接收消息m,得证。

(2)证明CRB2不重复和CRB3不创造特性。根据正则可靠广播的RB2不重复和RB3不创造特性,得证。

(3)证明CRB4一致特性。根据RB4一致特性,所有正确的进程的实例rb最终将接收消息m,因此实例crb最终将接收消息m,得证。

(4)证明CRB5因果接收特性。假设进程p的实例crb接收了消息m1,此时进程p的因果关系past中必然存在消息m1。然后当进程p的实例crb广播消息m2时,被同时广播的因果关系past中也必然有消息m1。当其他进程接收到消息m2和包括消息m1的因果关系mpast时,必然先接收消息m1、后接收消息m2,得证。

性能分析

本算法底层基于正则可靠广播实现,因此通信次数和消息个数复杂度均与正则可靠广播相同。但由于该算法在每次广播消息时需要一起发送因果关系,而因果关系是全局的,其大小随着整个系统中的广播次数的增加而增加,因此会导致每次广播所对应的消息大小越来越大。算法优化为了进一步优化消息大小,可以考虑优化因果关系的传递。

在本算法中,为了让消息接收进程无须等待消息m2所依赖的消息m1,而在因果关系链表中存储了整个系统中所有被广播过的消息,导致消息过大。

实际上,可以优化因果关系的描述以降低因果关系的大小。例如,当进程p在广播了消息m1,接收了消息m2、消息m3后,准备广播消息m4时,因果关系为[DEPEND,m1,m2,m3],即表示“消息m1、消息m2、消息m3因果先于消息m4”,那么接收进程只有在接收了消息m1、消息m2和消息m3之后才会接收消息m4。当进程p准备继续广播消息m5时,因果关系为[DEPEND,m4],即表示“消息m4因果先于消息m5”,那么接收进程只有在接收了消息m4之后才会接收消息m5,而为了接收消息m4,接收进程又必须先接收消息m1、消息m2和消息m3。又根据正则可靠广播的RB4一致特性,接收进程最终必然会接收消息m1、消息m2、消息m3和消息m4,因此最终必将接收消息m5。

不难看出,因果关系从原来的全体消息集合降低为所依赖的最小消息集合,算法的消息大幅减小了。同时,虽然该算法要求每个进程内部维护一个消息先后关系图,但消息先后关系图无须存储每个消息的内容,仅需存储消息的标识即可,因此对内存的开销也更小。

停止型失败算法

算法 6-11通过清除消息的方式实现了因果可靠广播实例crb,适用于停止型失败模型,韧性f小于进程总数N。对于每个已被接收的消息m,进程将调用正则可靠广播实例rb广播确认消息[ACK,m]给所有进程,而收到了确认消息[ACK,m]的进程会把消息发送进程记录在集合 ack[m]中。如果接收到了所有正确的进程发送的确认消息[ACK,m],则把消息m从因果关系past中删除,以减小消息存储和广播的大小。

显然,要确认自己已经接收到了所有正确的进程的确认消息,就必须知道哪些进程是正确的,因此需要使用完美失败检测实例P和一个用于表示所有正确的进程的集合correct。当某个进程p崩溃后,实例crb通过实例P检测出进程p已经崩溃了,然后将进程p从集合correct中删除。当correct⊆ack[m]时,说明已经接收到所有正确的进程发送的确认消息[ACK,m]了

正确性证明

(1)CRB1-CRB4的证明与算法 6-10相同,不再赘述。

(2)证明CRB5因果接收特性。在算法 6-10的CRB5因果接收特性的证明过程中,已经证明在没有垃圾回收时满足因果接收特性,现在只需要证明在引入垃圾回收过程后,仍然满足因果接收特性。

根据完美失败检测的PFD2强准确特性可知,如果进程的实例P检测出进程p,那么进程 p 必然崩溃了。因此当 correct⊆ack[m]时,说明所有正确的进程的实例 crb都已经接收了消息m,此时从自己的链表past中删除消息m并不会影响其他消息之间的因果关系,也不会影响消息m的接收。证毕。

性能分析

该算法在算法 6-10的基础上引入了垃圾回收,从而引入了确认消息,其复杂度为O(N 2)。但是确认消息的引入不会对消息的广播造成阻塞,它是一个异步的过程。此外,可以把多个确认消息合并成一个大的确认消息后进行广播,可以有效减少消息个数。

静音型失败算法:基于向量时间

算法 6-12基于向量时间实现了因果可靠广播实例crb,适用于静音型失败模型,韧性f小于进程总数N。实例crb使用正则可靠广播实例rb广播消息。

在每次广播消息时,还会附带一个向量时间作为这个消息的时间戳,用于描述该消息相对于其他消息的先后关系,保证接收进程接收消息的顺序不违反因果关系。

向量时间用数组表示,数组中的每个元素为对应进程的逻辑时间。在比较两个向量时间W和V时,如果两个数组的长度相等,即|W|=|V|,且对于任何i=1,2,…,N都有W[i]≤V[i]时,那么向量时间W小于等于向量时间V,即W≤V。

每个进程都持有一个向量时间,用数组 V 表示,其长度为进程的总数 N。函数rank 实现了进程全集Π和整数集合{1,2,…,N}之间的一一映射关系,使得 V[rank(p)]表示进程p的逻辑时间。在进程p持有的向量时间Vp中,Vp[rank(q)]表示进程p看到的进程q的逻辑时间,也就是进程p的实例crb已经接收的且由进程q广播的消息的个数。进程p的向量时间起初是[0]N,表示在进程p看到的每个进程的逻辑时间都是 0,即该进程从未接收过任何消息。同时,每个进程都有一个变量 lsn,表示进程本身的逻辑时间,即该进程已经广播了的消息的个数,初始值为0。

当实例crb广播消息m时,会将当前的向量时间V复制成一个临时向量时间W,并将W[rank(self)]赋值为变量lsn,然后对变量lsn加1。实例crb最终通过实例rb广播消息m及其向量时间W。

当进程的实例rb接收消息m时,先把消息m及其向量时间W暂存在集合pending中。然后检测pending集合,如果集合pending中存在某个消息m'的向量时间W'小于或等于本进程的向量时间V,那么实例crb就接收该消息m',并将向量时间V中消息m'的广播进程p'的逻辑时间加1,表示又多了一个已被实例crb接收的且由进程p'广播的消息。

正确性证明

(1)证明CRB1有效特性。当一个正确的进程p通过实例crb广播了消息m时,实例crb将通过实例rb广播消息m及时间戳W。根据正则可靠广播的RB1有效特性,进程p最终将通过实例rb接收消息m及向量时间V。在这个过程中,进程p可能通过实例crb接收了其他的消息,但这只会让进程p的向量时间V的某些元素变大,而不会变小。因此,只要进程p的实例rb接收了消息m以及时间戳W,就必然因W≤V成立使得实例crb接收消息m。所以满足CRB1有效特性。

(2)证明CRB2不重复和CRB3不创造特性。根据正则可靠广播的RB2不重复和RB3不创造特性,得证。

(3)证明CRB4一致特性。假设进程p的实例crb接收了消息m及时间戳W,那么进程p的实例rb必然接收了消息m及时间戳W。根据正则可靠广播的RB4一致特性,正确的进程q的实例rb必然最终接收消息m及时间戳W。若进程q的实例crb不能马上接收消息m,必然是因为“消息m的时间戳W≤进程q的向量时间V”不成立,这说明在进程p广播消息m之前接收了其他进程广播的消息(用集合S表示),导致消息m的时间戳W的某些元素大于进程q的向量时间V对应的元素。但是根据正则可靠广播的RB4一致特性,被进程q的实例rb最终会接收集合S中的消息,使得进程q的向量时间V大于等于消息m的时间戳W最终成立,使得进程q的实例crb最终接收消息m。所以,只要正确的进程p接收了消息m,那么正确的进程q就会接收消息m,满足CRB4一致特性。

(4)证明CRB5因果接收特性。假设消息mi因果先于消息mj,根据3.5.3节中因果关系的定义,有三种消息mi因果先于消息mj的情况。

情况一:进程p先广播消息mi、后广播消息mj。由于广播进程p的实例crb在每次广播消息时会将变量lsn加1;根据CRB2不重复特性,接收进程q的实例crb最多接收一次;接收进程q的实例crb每次接收进程p广播的消息时仅将V[rank(p)]加1,因此进程q只可能按照进程p广播的顺序接收消息。

情况二:进程p在接收了消息mi后广播消息mj。假设消息mi是进程q广播的,进程p的实例crb能接收消息mi,则说明进程p的向量时间V大于等于消息mi的时间戳。由于进程 p 在接收消息 mi后会对 V[rank(q)]加 1,因此消息mj的时间戳的W[rank(q)]肯定比消息mi的时间戳的W[rank(q)]大。对于任何进程r而言,如果没有接收消息 mi,那么进程 r 的向量时间的 V[rank(q)]必然小于消息 mj的时间戳的W[rank(q)],所以不会接收消息 mj。因此,进程 r 不会先接收消息 mj、后接收消息mi。

情况三:存在消息mk,且消息mi因果先于消息mk,消息mk因果先于消息mj。在这种情况下,总能递归地对mi和mk的先后关系、mk和mj的先后关系进行分解,总而得到一系列属于情况一或情况二的先后关系。那么根据情况一和情况二可知,任何进程必然先接收消息mi,然后接收消息mk,最后接收消息mj。

故满足CRB5因果接收特性,得证。

性能分析

该算法在广播消息时仅需附带一个向量时间,其大小为系统中的进程总数N。该算法的通信次数、消息个数的复杂度与底层的正则可靠广播相同。

算法变体——因果统一可靠广播

其实,只要把算法 6-12中的正则可靠广播实例rb替换为算法 6-5实现的统一可靠广播实例urb,就可以实现抽象 6-8定义的因果统一可靠广播实例curb,同样适用于静音型失败模型,韧性f小于进程总数N。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值