分布式系统:协调与协定


在发生故障时,分布式系统中的进程需要协调它们的动作和对共享值达成协定。

故障检测器

假定进程之间通过可靠通道连接,故障由可靠通信协议屏蔽,且进程故障不影响其他进程的通信能力,可靠通道保证消息最终送达接收缓冲区。在同步系统中,通道还具备硬件冗余以确保在指定时间内完成传递。通信可能因网络分区或非对称、非传递连接而局部受限,但可靠性的假设涵盖故障链路的修复或规避,尽管并非所有进程都能同时通信。简单起见默认进程仅发生崩溃故障,正确进程指在整个运行期间无故障的进程,崩溃进程即使在某阶段无故障也不被视为正确。
为实现分布式系统中应对进程崩溃的协调算法,关键挑战之一在于如何准确判断进程是否已发生故障。故障检测器作为处理此类查询的服务,通常以进程本地对象的形式存在,并与其他进程的对应组件协同执行检测算法。故障检测器还分为不可靠的和可靠的,它们能提供的信息如下表所示。不可靠故障检测器仅能提供 Unsuspected 或 Suspected 两类提示性结果,这些结果可能并不精确反映实际故障状态。可靠的故障检测器能够精确识别故障,其应答除 Unsuspected 外还可返回确定的 Failed 结论,表明进程确已崩溃且不可恢复。

故障信息类型 说明
Unsuspected 最近已收到表明进程没有故障的证据(例如最近从该进程收到一个消息),但是那个进程之后出现故障
Suspected 表示有迹象表明进程可能已经出故障了(例如存在超时等异常迹象),但也可能是网络分区或进程延迟导致的,产生误判
Failed 表示检测器确定进程已崩溃

故障检测器的应答本质上是基于各进程本地可用信息生成的,在分布式的情况下由于通信条件差异,不同进程可能获得不一致的检测结果。不可靠故障检测器可能存在误报(怀疑正常进程)或漏报(未发现故障进程),即兼具不精确与不完全的缺陷;可靠的故障检测器虽能精准判定故障,却要求系统具备同步性,这在现实系统中往往难以满足。不过即使是不可靠的故障检测器,只要具备特定的良构特性,仍能为处理故障时的进程协调问题提供解决方案。

分布式互斥

分布式进程在协同工作时常常需要对共享资源进行互斥访问,以避免干扰并确保一致性,这本质上与操作系统中的临界区问题相似。但在分布式环境下,由于缺乏共享变量或统一本地内核设施的支持,传统的互斥方法不再适用,因此需要设计一种完全基于消息传递的解决方案。

互斥的要求和评价

对互斥有如下表所示的 3 个要求,其中 ME2 意味着系统必须避免死锁(多个进程因相互依赖而无法继续推进)与饥饿(某一进程的请求被无限期推迟)。ME3 条件不仅防止了等待进程被重复插队,也支持进程间对临界区访问的协调,例如多线程进程可在等待期间发送消息触发其他进程的访问请求,ME3 能确保初始请求者优先获得进入权限。

互斥要求 说明
ME1 安全性 在临界区一次最多有一个进程可以执行
ME2 活性 进入和离开临界区的请求最终成功执行
ME3 顺序性 如果一个进入临界区的请求发生在先,则进入临界区时仍按此顺序

评价互斥算法性能的主要标准如下表所示,其中在进程间存在必要通信的前提下,吞吐量可通过一个进程离开临界区到下一个进程进入临界区之间的间隔时间来间接评估,同步延迟越短则系统吞吐量通常越高。

互斥算法性能标准 说明
带宽消耗 以每次进入和退出临界区操作所需发送的消息数量来衡量
客户延迟 指单个进程在执行进入或退出操作时所经历的时间延迟
系统吞吐量 一组进程整体访问临界区的频率

中央服务器算法

中央服务器算法是一种实现互斥的简单方法,其核心是由一个中心服务器负责管理进入临界区的许可。具体流程如下:

  1. 进入临界区:进程向服务器发送请求消息并等待应答。若当前无其他进程持有令牌,服务器立即应答授予令牌;若有进程持有令牌,则将该请求加入等待队列。
  2. 离开临界区:进程离开时向服务器发送消息交还令牌。若等待队列非空,服务器选择队列中最早的请求,将其移除并应答对应的进程,使该进程获得令牌。

一个中央服务器的请求队列样例如下图所示:首先 p2 请求进入队列,此时队列已包含 p4 的请求所以其排在 p4 后面,p3 离开后服务器应答 p4 并允许其进入。
image
在无故障假设下,该算法能够满足 ME1 和 ME2 条件,然而它无法保证 ME3 条件。算法性能方面,进程进入临界区时即使没有其他进程占用,也需要请求与授权两次消息交互,因此会经历一次往返通信延迟。离开临界区仅需发送一条释放消息,在异步通信模型下不会对离开进程造成延迟。同步延迟具体为从释放消息发送到服务器、到服务器向下一进程发出授权消息完成一次往返的耗时。

基于环的算法

基于环的互斥算法通过将 N 个进程组织成一个逻辑环来实现,每个进程仅需与环中下一个进程通信。与令牌环网络的原理类似,互斥通过一个沿环单向传递的令牌消息来实现:不需要进入临界区的进程收到令牌后立即转发;需要进入的进程则保留令牌,进入临界区使用后,再将令牌传递给邻居。
image

该方法满足安全性(ME1)和活性(ME2),但令牌传递不严格遵循发生在先顺序,且无论是否有进程需要进入临界区,令牌都会持续在环中传递,从而持续占用网络带宽。请求进入临界区的延迟范围在 0(刚收到令牌)到 N(刚传出令牌)次消息传递之间,退出临界区仅需一次消息传递,同步延迟在 1 到 N 次消息传输之间变化。

基于组播和逻辑时钟的算法

Ricart 和 Agrawala 提出了一种基于组播和逻辑时钟的互斥算法,适用于 N 个对等进程。算法的核心思想是:希望进入临界区的进程向所有其他进程组播请求消息,只有在收到所有其他进程的应答后才被允许进入,进程之间的应答机制旨在确保满足 ME1、ME2 和 ME3 条件。每个进程具有唯一的数字标识符,彼此间通过可靠通道通信,并维护一个依据 Lamport 时钟规则更新的逻辑时钟。请求消息格式为 <T, p>,其中 T 是发送方的时间戳,p 是发送方的标识符。每个进程使用状态变量记录其当前状态,包括:RELEASED(在临界区外)、 WANTED(希望进入)、HELD(在临界区内)。
当进程请求进入临界区时,如果所有其他进程的状态均为 RELEASED,它们会立即应答,请求者随即进入。如果有进程处于 HELD 状态,则该进程在离开临界区前不会应答,从而阻止请求者进入。当多个进程同时请求时,具有最小时间戳(即 Lamport 时钟最早)的请求将优先获得全部 N-1 个应答并进入;若时间戳相同,则按进程标识符排序决定优先级。需要注意的是,进程在发送自己的请求并记录其时间戳 T 之前,会推迟处理其他进程的请求,以确保在处理请求时能做出一致的决定。
考虑三个进程 p1、p2 和 p3 的示例。假设 p3 不打算进入临界区,而 p1 和 p2 同时发出请求,且请求的时间戳分别为 41(p1)和 34(p2):

  1. p3 在收到 p1 和 p2 的请求后,由于自身状态为 RELEASED,因此立即向两者回复应答。
  2. p2 收到 p1 的请求时,发现自己的请求时间戳更早(34 < 41),因此暂不回复 p1,将其请求搁置。
  3. p1 收到 p2 的请求时,发现对方请求时间戳更早,于是立即向 p2 回复应答。
  4. p2 在收到来自 p1 和 p3 的应答(共 N-1 = 2 个)后,即可进入临界区。
  5. 当 p2 离开临界区时,它会向之前被搁置的 p1 发出应答,p1 在收到全部应答后也得以进入临界区。

image

该算法在获得进入许可时通常需要 2(N-1) 条消息:通过组播发送 N-1 条请求消息,并接收 N-1 条应答。若硬件支持组播,则请求只需一条消息,总消息数可降至 N 条。在带宽消耗方面,此算法高于之前介绍的算法。然而其客户延迟仅为一个往返时间(忽略组播请求的延迟),且同步延迟仅为单次消息传输时间,相较于前两种算法具有明显优势。

Maekawa 投票算法

Maekawa 投票算法的核心思想是:进程进入临界区无需获得所有对等进程的同意,只需从其关联的特定进程子集中获得许可即可。该算法将进入临界区视为一种选举过程,候选进程必须收集到足够的选票(即许可)才能进入。为确保互斥的安全性(ME1),算法设计的关键在于,任意两个进程的投票集必须存在交集。这样,位于交集内的进程每次只能将选票投给一个候选者,从而防止多个进程同时进入临界区。考虑由 N 个进程 P1, P2, ..., PN 组成的系统,每个进程 Pi 都有一个预先定义好的、固定不变的请求集记作 Ri。Ri 需要满足一下要求:

  1. Ri 是系统中所有进程的一个子集。
  2. Pi 必须向 Ri 中的每一个成员都请求并获得许可后,才能进入临界区。
  3. Pi 本身必须在自己的 Ri 中,即 Pi ∈ Ri。

请求集 Ri 必须满足以下两个相交特性来确保互斥:

  1. 两两相交:对于任意两个进程 Pi 和 Pj,它们的请求集必须有交集。这保证了不会有多个进程同时获得进入临界区的许可。
  2. 平等性:每个进程在其请求集中的“份量”应大致相等,通常体现为每个 Ri 的大小 K 是相同的,每个进程出现在其他进程请求集中的次数 L 也是相同的。

此时假设进程 Pi 和 Pj 都试图进入临界区。由于 Ri ∩ Rj ≠ ∅,那么至少存在一个公共的进程 Pk。Pk 一次只能给一个请求者发放许可(例如,通过一个本地锁),因此 Pi 和 Pj 不可能同时获得 Pk 的许可,从而它们不可能同时进入临界区。算法基本流程如下:每个进程 Pi 维护一个状态机(RELEASED, WANTED, HELD)以及对来自其他进程请求的队列。当进程 Pi 想要进入临界区时:

  1. 请求阶段:Pi 将自己的状态设为 WANTED,然后向其请求集 Ri 中的每一个进程 Pj 发送一个 REQUEST 消息(包含时间戳 ts)。
  2. 投票阶段:收到请求的进程 Pj 将维护一个本地锁和一个请求队列。当 Pj 收到 Pi 的 REQUEST 消息时,如果 Pj 的锁是空闲的并且其请求队列为空,则 Pj 立即将锁授予 Pi,并向 Pi 回复一个 GRANT 消息;当锁已被占用或有更早的请求在排队时,Pj 将 Pi 的请求放入队列(按时间戳排序)。
  3. 进入阶段:请求者 Pi 等待,直到它收到来自其请求集 Ri 中所有成员的 GRANT 消息。一旦收齐所有许可,Pi 的状态变为 HELD 并进入临界区执行。
  4. 释放阶段:Pi 退出临界区后状态变为 RELEASED,然后向其请求集 Ri 中的每一个进程 Pj 发送一个 RELEASE 消息。
  5. 锁传递阶段:当 Pj 收到 Pi 的 RELEASE 消息后将 Pi 从队列中移除,检查队列头部是否有下一个等待的请求(比如来自 Pk)。如果有,Pj 将锁授予 Pk,并向 Pk 发送 GRANT 消息,同时将 Pk 移出队列(或标记为已服务)。

该算法的优点是通信开销低,每次请求和释放都只与 K 个节点通信,而 K 通常为 O(√N),远小于 N。 如果两个进程的请求集不相交,它们可以并行地收集选票,提高了系统吞吐量。但是该算法存在死锁风险,例如三个进程 P1, P2, P3 各自获得了部分许可,但都在等待对方释放某个公共的许可导致循环等待。由于需要处理死锁避免机制,实际代码比描述的基本流程复杂。

容错

在容错性方面,对上述算法的评估主要关注两点:消息丢失和进程崩溃的影响。消息丢失方面,若通信通道不可靠,所有介绍过的算法均无法容忍消息丢失。进程崩溃方面的容错性如下:

算法 容错性
基于环的算法 不能容忍任何单个进程的崩溃故障
Maekawa 投票算法 可以容忍部分进程崩溃,前提是崩溃进程不在当前所需的投票集中
中央服务器算法 能够容忍既不持有令牌也未请求令牌的客户进程崩溃
Ricart 和 Agrawala 算法 可通过修改(例如隐式授权所有请求)使其能够容忍进程的崩溃故障

选举

选举算法用于从一组进程中选出一个唯一的进程来承担特定角色,例如在“中央服务器”互斥算法变种中选举服务器,其基本要求是所有进程对该选择达成一致。若担任该角色的进程不再适合,需通过再次选举选择替代者。一个进程若发起一次选举,则称其召集选举。每个进程每次最多召集一次选举,但理论上 N 个进程可并发发起 N 次选举。在任何时刻,一个进程可以是参与者(即正在参与某次选举运行)或非参与者(当前未参与任何选举)。

选举算法的要求

选举算法的关键要求是:即使多个进程并发召集选举,选出的进程也必须是唯一的。 通常约定选择具有最大标识符的进程作为当选者,标识符可以是任何唯一且可全序排序的值,例如通过 <1/load, i> 组合(其中 load>0 且进程索引 i 用于对负载相同的标识符排序)可以选择负载最小的进程。每个进程 pᵢ 维护一个变量 electedᵢ,用于记录当选进程的标识符。进程首次成为参与者时,将该变量设为特殊值“⊥”表示未定义。算法运行需满足以下条件:

选举要求 说明
E1 安全性 在任何一次选举运行中,参与的进程 pᵢ 在运行结束时满足 electedᵢ = ⊥ 或 electedᵢ = P,其中 P 是运行结束时具有最大标识符且未崩溃的进程。
E2 活性 所有参与进程 pᵢ 最终要么设置 electedᵢ ≠ ⊥,要么自身崩溃。非参与进程的 electedᵢ 可能仍记录着前次当选进程的标识符。

选举算法的性能通常从如下两个方面进行衡量:

选举算法性能标准 说明
总网络带宽消耗 与发送消息总数成正比
回转时间 从算法启动到终止之间的串行消息传输次数

基于环的选举算法

基于环的选举算法适用于按逻辑环排列的一组进程,每个进程向其顺时针邻居传递消息。算法假定系统异步且无故障,目标是从环中选出具有最大标识符的进程作为协调者。算法初始时,所有进程标记为非参与者。任何进程均可发起选举,将自己标记为参与者,并将自己的标识符放入选举消息发送给顺时针邻居。当进程收到选举消息时:

  • 若消息中的标识符大于自身标识符,则转发该消息。
  • 若消息中的标识符小于自身标识符,且接收进程为非参与者,则用自己的标识符替换消息中的标识符后转发;若已是参与者,则不转发。
  • 在转发消息时,进程将自身标记为参与者。

若收到的标识符与自身标识符相同,说明自身标识符最大,该进程即成为协调者。协调者将自身重新标记为非参与者,并向邻居发送当选消息,其中包含自己的标识符。当进程收到当选消息时,将自己标记为非参与者,将 elected 变量设为消息中的标识符,并将消息转发给邻居(除非自身是新的协调者)。一个基于环的选举的例子如下图所示,选举从 17 开始并将参与过的进程用蓝色标注,选举消息当前包含 24,但进程 28 会在消息到达时,把它替换为自己的标识符。
image

该算法满足安全性(E1),因为进程只有在收到自己的标识符时才会宣布当选,且所有标识符均被比较。任意两个进程中,标识符较大者不会转发较小者的标识符,因此不可能有两个进程同时宣布当选。活性(E2)由环的遍历和无故障假设保证。非参与者与参与者状态的设置有助于抑制并发选举产生的冗余消息。
在单进程发起的最坏情况下,若其逆时针邻居具有最大标识符,则选举消息需 N-1 步到达该邻居,再经 N 步完成回路以宣布当选,随后当选消息传递 N 次,总计 3N-1 条消息。回转时间同样为 3N-1,因为消息均为顺序发送。该算法本身不容错,但理论上可通过可靠的故障检测器在进程崩溃时重构逻辑环来增强鲁棒性。尽管如此,其容错能力的局限性降低了在实际系统中的直接应用价值。

霸道算法

霸道算法允许在选举期间发生进程崩溃,并假定进程间消息传递可靠,但要求系统是同步的,即通过超时机制来检测故障。与基于环的算法不同,该算法要求每个进程知晓所有具有更大标识符的进程,并能与它们通信。算法涉及三种消息类型:

霸道算法消息类型 说明
选举消息 用于发起选举
应答消息 用于回复选举消息
协调者消息 用于宣布当选进程的身份

进程通过超时发现协调者故障,随即发起选举,多个进程可能同时发起。此时存在两种情况:

  1. 已知自身具有最大标识符的进程直接向所有较小标识符的进程发送协调者消息,宣布自己当选。
  2. 具有较小标识符的进程发起选举时,向所有较大标识符的进程发送选举消息,并等待应答(等待时长上限为 \(T = 2T_{trans} + T_{proc}\),其中 \(T_{trans}\) 为最大消息传输延迟,\(T_{proc}v\) 为最大处理延迟)。
    • 若在 \(T\) 内未收到任何应答,则该进程认为自己是协调者,并向所有较小标识符的进程发送协调者消息。
    • 若收到应答,则等待另一个 \(T\) 时长以接收协调者消息;若仍未收到,则重新发起选举。

进程收到协调者消息后,将本地的 elected 变量设为消息中的协调者标识符。进程收到选举消息时,立即回复应答消息,并同时发起一次新的选举(除非已发起过)。当新进程启动以替代崩溃进程时,会直接发起选举。若其标识符最大,它将宣布自己为协调者,即使当前已有协调者正常运行,这是算法被称为“霸道”的原因。

一个霸道算法的运行样例如下图所示,四个进程 \(p_1\) 至 \(p_4\),\(p_1\) 检测到协调者 \(p_4\) 故障并发起选举(阶段1)。\(p_2\) 和 \(p_3\) 收到选举消息后回复应答并各自发起选举;\(p_2\) 收到 \(p_3\) 的应答,但 \(p_3\) 未收到故障进程 \(p_4\) 的应答(阶段2)。\(p_3\) 因而认定自己为协调者,但在发送协调者消息前也故障(阶段3)。当 \(p_2\) 的超时周期 \(T\) 结束后(假设早于 \(p_1\) 的超时),它因未收到协调者消息而重新发起选举,最终 \(p_2\) 当选为协调者(阶段4)。
image
基于可靠消息传递的假设,该算法显然满足 E2。在无进程被替换的情况下,算法也满足 E1,因为具有较小标识符的进程在发现较大标识符进程存在时会服从对方,所以不可能出现两个进程同时自认为协调者的情况。然而,若崩溃的进程被一个具有相同标识符的新进程替换,算法可能无法保证 E1。例如,当原进程 p 被替换时,新进程可能基于自身标识符最大而宣布成为协调者,而此时另一个已检测到 p 崩溃的进程也可能同时做出同样的决定。由于消息传递顺序无法保证,不同进程可能收到矛盾的协调者宣布消息,从而对谁是协调者得出不同结论。

此外,如果超时设置不准确(即故障检测器不可靠)或系统同步假设不成立(例如进程运行异常缓慢),同样可能导致安全性条件被破坏。假设进程 p3 未崩溃但运行极慢,或崩溃后被替换。当 p3(或其替换者)发送协调者消息时,p2 也可能同时发送自己的协调者消息。p2 可能在发送后收到 p3 的消息,从而设置 elected₂ = p3;而 p1 可能先收到 p3 的消息后收到 p2 的消息,最终设置 elected₁ = p2,这就违反了 E1 所要求的一致性。

在性能方面,算法的最佳情况是标识符次大的进程最先检测到协调者故障,它可直接宣布自己为协调者并发送 N-2 条协调者消息,此时回转时间仅为一条消息的传输时间。最坏情况则是最小标识符的进程首先发起选举,导致 N-1 个进程相继参与,每个进程均向所有较大标识符的进程发送消息,总消息复杂度为 O(N²)。

协定问题

共识问题、拜占庭将军问题及交互一致性问题,统称为协定问题。其核心是在一个或多个进程提出某个值后,使所有进程对该值达成一致。这类问题在分布式系统中普遍存在,例如:

  • 金融交易中计算机需对借贷操作达成一致;
  • 互斥算法中进程需对进入临界区的进程达成协定;
  • 选举算法中需决定当选者;
  • 全排序组播中需确定消息传递顺序。

系统模型和问题定义

系统包含一组通过消息传递通信的进程 \(p_i \ (i=1,2,\dots,N)\),假设通信可靠但进程可能发生故障,并假设至多 \(f\) 个进程可能出错。

共识问题

公式问题中每个进程 \(p_i\) 初始处于未决状态,并提议一个属于集合 \(D\) 的值 \(v_i\)。进程通过通信交换值后,设置一个决定变量 \(d_i\),随后进入决定状态且不再更改 \(d_i\)。图15-16展示了三个进程参与共识的示例:两个进程提议“继续”,第三个提议“放弃”后崩溃,剩余两个正确进程均决定“继续”。
image

共识算法需满足以下条件:

共识算法条件 说明
终止性 每个正确进程最终设置其决定变量
协定性 所有正确进程的决定值相同,即若 \(p_i\) 和 \(p_j\) 正确且已进入决定状态,则 \(d_i = d_j\)
完整性 若所有正确进程提议同一值,则任何进入决定状态的正确进程必须选择该值

若系统无故障,每个进程可靠组播其提议值,收集全部 \(N\) 个值后计算函数。由于组播可靠且每个进程收到相同值集合,它们计算相同函数结果,从而满足协定性与完整性。若进程可能崩溃,尤其在异步系统中,故障检测与算法终止性面临挑战。若进程出现拜占庭故障,故障进程可能发送任意不一致的值,正确的进程需通过比对自身接收值与其他进程声明的接收值来应对恶意行为。
交互一致性是共识问题的另一种变体,每个进程均提议一个值,目标是使所有正确进程就一个决定向量达成一致,该向量的每个分量对应一个进程的提议值。交互一致性的完整性要求为若进程 \(p_i\) 正确,则所有正确进程的决定向量中第 \(i\) 个分量必须等于 \(p_i\) 的提议值。

拜占庭将军问题

拜占庭将军问题描述了多个将军(进程)中可能存在叛变者(故障进程)时如何协调行动。具体情境为:一位司令(发送进程)发出进攻或撤退命令,其他中尉(接收进程)需决定是否采纳该命令。叛变的司令可能向不同中尉发送矛盾命令;叛变的中尉则可能向同伴传递虚假信息。
image
该问题与共识问题的区别在于:拜占庭将军问题由单一进程(司令)提供值,其他进程决定是否采纳;而共识问题中每个进程均可提议值。拜占庭将军问题的要求中,终止性和协定性和共识问题一样,完整性要求若司令是正确的,则所有正确进程必须采纳司令提议的值。注意当司令正确时,完整性已隐含协定性,但司令不一定正确。

同步系统中的共识问题

同步系统中解决共识问题的算法依赖基本组播协议,并假设在 N 个进程中最多有 f 个进程可能出现崩溃故障。算法的核心思想是:每个正确的进程通过多轮通信收集其他进程的提议值。算法进行 \(f+1\) 轮交换,每一轮中正确的进程通过 B-multicast 发送其已知的值集合。即使在最坏情况下所有 f 个进程都崩溃,算法仍能确保在 \(f+1\) 轮结束后,所有存活的正确进程达到一致的状态。算法流程为:

  1. 在第 r 轮开始时,进程 \(p_i\) 将其当前已知的提议值集合存储在变量 \(Values_i[r]\) 中。
  2. 每个进程将上一轮未发送过的值集合组播出去。
  3. 进程接收其他进程组播的类似消息,并记录新值。
  4. 经过 \(f+1\) 轮后,每个进程选择其所收到的最小值作为决定值。

算法的正确性方面:

  • 终止性:由于系统是同步的,算法显然能在有限轮数内结束。
  • 协定性与完整性:需证明所有正确进程在最后一轮结束后拥有相同的值集合,然后通过对该集合应用最小值函数(或其他确定函数)达成一致。假设两个正确进程 \(p_i\) 与 \(p_j\) 的最终值集合不同,例如 \(p_i\) 包含值 \(v\) 而 \(p_j\) 不包含。这意味着存在某个进程 \(p_k\) 曾在某轮将 \(v\) 发送给 \(p_i\),但未能在后续轮中将其发送给 \(p_j\) 便已崩溃。同理可追溯至更早的轮次,每轮至少有一个进程因崩溃而未能传播该值。然而算法进行了 \(f+1\) 轮,而最多只有 f 个进程可能崩溃,由此推出矛盾。因此所有正确进程的最终值集合必定相同。

同步系统中的拜占庭将军问题

假设进程可能出现随机(拜占庭)故障,即故障进程可能发送任意消息或故意沉默。假设 N 个进程中最多有 f 个可能故障,且信通道是私有且可靠的,正确进程之间的通信不会被故障进程篡改。正确的进程可以通过超时检测消息丢失,但无法区分发送者是暂时沉默还是已经崩溃。

3 个进程的不可能性

Lamport 等人证明,在未使用数字签名的情况下:对于 3 个进程且允许 1 个故障,无法满足拜占庭将军问题的条件。下图展示了两个场景,每个场景涉及 3 个进程(p1 为司令,p2 和 p3 为中尉),且只有一个进程故障:

  1. 中尉 p3 故障:司令 p1 正确发送值 v 给 p2 和 p3。p2 正确转发 v 给 p3,但 p3 向 p2 发送错误值 v'≠v。此时 p2 收到两个不同值,无法判断哪个来自司令。
  2. 司令 p1 故障:故障司令向 p2 发送 x,向 p3 发送 y(x≠y)。p3 将收到的 y 转发给 p2,导致 p2 再次收到两个不同值。

在这两种场景下,p2 的观察状态完全相同。如果存在解,则当司令正确时(场景一),p2 必须决定 v(完整性要求)。但由于无法区分场景,p2 在场景二中也必须选择司令发送的值(即 x)。同理,p3 在场景二中也必须选择司令发送的值(即 y)。但这导致 p2 和 p3 的决定值不同(x ≠ y),违反了协定性条件。因此,不存在解决方案。
image

该结论可以进一步推广到 N ≤ 3f 的情况,此时不存在解决方案。假设存在 N ≤ 3f 时的解决方案,将 N 个将军分组为三个进程 p1、p2、p3 分别模拟 n1、n2、n3 个将军(满足 n1 + n2 + n3 = N 且每组规模 ≤ N/3 ≤ f)。假设三个进程中有一个故障,正确的进程模拟正确的将军,故障进程可能模拟故障将军。由于 N ≤ 3f,模拟中最多有 f 个将军可能出错。如果模拟算法正确,正确的将军将达成一致并满足完整性。但这意味着两个正确的进程(模拟正确将军的进程)达成了共识——每个进程都能决定所有将军的选择值。然而,这与“3个进程中有一个故障时无法达成共识”的结论矛盾。因此,N ≤ 3f 时不可能有解。

评价解决拜占庭将军问题的算法效率,通常基于两个指标:

拜占庭将军问题的效率指标 说明
消息传递轮数 影响算法终止时间
消息数量与长度 影响带宽消耗与执行时间

对于未签名消息,算法一般需要 f+1 轮,每轮中每个进程需转发前一轮收到的值子集,消息复杂度高达 O(N^{f+1})。Fischer 和 Lynch 证明,对于拜占庭故障,任何确定性共识算法至少需要 f+1 轮,因此在轮数上该算法已达最优,但消息复杂度可通过改进降低。部分算法使用数字签名同样需 f+1 轮,但消息数量降至 O(N^2)。由于算法复杂且开销大,通常仅建议在安全威胁严重的场景中使用。

口信消息方案

口信消息型解决方案方法基于 Lamport、Shostak 和 Pease 提出的口头消息模型,这里的“口信”指代一种具有特定限制的消息。叛徒可以任意篡改他转发的任何消息,口信消息的定义如下:

  • A1.任何已经发送的消息都将被正确传达;
  • A2.消息的接收者知道是谁发送了消息;
  • A3.消息的缺失可以被检测。

算法的运行是一个递归过程,用 OM(m) 表示,其中 m 是叛徒的最大数量。

  • OM(0) :即没有叛徒,司令将他的命令发送给每个副官,每个副官采用他收到的命令作为最终决定。
  • OM(m) :假设最多有 m 个叛徒,司令将他的命令发送给每个副官。对于每个副官 i 从司令收到的命令为 vi,如果没有收到命令,则默认为“撤退”。然后副官 i 扮演新的“司令”,运行 OM(m-1) 协议,将 vi 发送给其余的 n-2 个副官(除了他自己和原始司令)。对于每个副官 i,他从其他每个副官 j 那里都收到一个命令(来自第 2 步的 OM(m-1) 过程),他采用所有命令中的多数命令作为自己的最终决定。

以 4 个进程的情况为例,下图两种场景:

  1. 中尉 p3 故障:正确中尉 p2 和 p4 分别计算 majority(v, v, u) = v 和 majority(v, v, w) = v,达成一致。
  2. 司令 p1 故障:正确中尉 p2、p3、p4 均计算 majority(v, u, w) = ⊥(特殊值 ⊥ 表示无多数值),从而达成一致。

image

该算法要求将军总数 n > 3m,也就是说要容忍 m 个叛徒,系统至少需要 3m+1 个节点。因为在一个“司令是叛徒”的场景中,忠诚的副官们需要通过与其他副官交叉核对来发现真相。如果叛徒数量过多,他们可以联合起来在忠诚的副官中制造虚假信息。这种方法的消息数量随递归呈指数级增长,运行 OM(m) 需要发送 O(nm) 条消息,通信开销极大,不适用于大规模实际系统。

签名消息方案

签名消息方案引入了签名消息模型,通过密码学技术极大地简化了问题。 签名消息的定义是在口信消息定义的基础上增加了如下两条,使得签名消息无法被伪造或者篡改。数字签名使用如 RSA、ECDSA 的算法实现,如果叛徒发送了矛盾的消息,他签过名的消息会成为他篡改消息的证据。

  • A4.忠诚将军的签名无法伪造,而且对他签名消息的内容进行任何更改都会被发现;
  • A5.任何人都能验证将军签名的真伪。

算法的核心思想是利用签名收集证据,使用 SM(m) 表示:

  1. 司令用他的私钥签署命令,然后发送给所有副官。
  2. 对于每个副官 i,如果他收到的是一个新的、经过签名的命令(即来自司令,或者是一个包含司令签名的命令链),他会将这个命令加入自己的集合 Vi。如果他还没有将这个命令转发给其他人,他会附上自己的签名,然后转发给所有其他副官。
  3. 决策规则为副官 i 不再使用多数决,而是检查他的命令集合 Vi。如果集合中存在且仅存在一个命令(例如 attack),且该命令的签名链包含了司令和其他至少 m 个副官的签名,说明至少有 m+1 个节点传递了此命令,足以抵抗 m 个叛徒的干扰,则采用该命令。否则使用默认命令(如 retreat)。

签名消息方案放宽了对节点数量的要求,要容忍 m 个叛徒,系统至少只需要 m+2 个节点,比口信型方案的 3m+1 少得多。叛徒无法传播矛盾的消息,一旦他试图签署另一个矛盾的消息,其他忠诚的将军可以用他的签名作为证据揭露,消除了“口头消息”模型中叛徒对不同人说不同话的复杂攻击可能。消息数量是 O(n2) 级别的,远低于口信型的指数级增长。但是该方案依赖于一个安全的公钥基础设施来管理密钥和签名算法,如果签名被破解(如量子计算威胁),方案的安全性将崩溃。

异步系统的不可能性

在异步系统中,即使仅有一个进程发生崩溃故障,也不存在能确保达成共识的算法。其根本原因在于异步系统中无法区分一个进程是运行缓慢还是已经崩溃,这使得算法执行可能被无限延迟,从而阻止共识达成。由此结论可直接推出在异步系统中,拜占庭将军问题、交互一致性问题以及全排序和可靠组播问题同样无法确保解决。不过,不可能性结论针对的是“确保”达成共识,在实际系统中共识仍可能以概率大于零的方式达成。绕过不可能性结论的一种途径是考虑部分同步系统,其同步性强于异步系统而弱于完全同步系统,使得共识问题可解。

异步系统共识方法 介绍
故障屏蔽 即通过完全屏蔽进程故障来避免问题。例如,事务系统使用持久存储实现崩溃恢复:进程在关键点将状态保存至持久存储,崩溃重启后可从中断处继续执行,从而表现为仅偶尔执行缓慢的正确进程。
使用故障检测器 虽然纯异步系统中无法实现完美的故障检测器,但进程可协商认定超时未响应的进程为故障,并将其消息忽略,从而将系统“转化”为同步系统。
随机化 “敌人”本质上是一个随机事件的组合,随机化方法通过引入一个关于进程行为的可能性元素,使得阻碍不能影响到系统运行。

其中第二种方法要求故障检测器具备高精确性,否则可能错误排除正常进程。一种思路是使用不完美的故障检测器,允许被怀疑的进程继续正常运行。在通信可靠且崩溃进程不超过 N/2 的异步系统中,借助最终弱故障检测器可实现共识。该检测器需满足每个故障进程最终常被某些正确进程怀疑,且存在某个时刻后至少一个正确进程从未被其他正确进程怀疑。

参考资料

《分布式系统概念与设计》[英]George Coulouris, Jean Dollimore, Tim Kindberg, Gordon Blair,金蓓弘, 马应龙 译,机械工业出版社

posted @ 2026-02-07 03:08  乌漆WhiteMoon  阅读(65)  评论(0)    收藏  举报