分布式互斥算法

1. 概述:什么是分布式互斥

假设有两个小孩想玩同一个玩具(临界资源),但玩具只有一个,必须保证一次只有一个人能够玩。当一个小孩在玩时,另一个小孩只能原地等待,直到玩完才能轮到自己。这就是 互斥(Mutual Exclusion)概念:任意时刻,只允许规定数量的进程(或节点)访问临界区,其余进程只能排队等待。

  • 临界资源:在例子中即为玩具;在分布式系统中可能是某个文件、数据库记录、硬件设备等。
  • 竞态:多个进程同时争夺同一个资源时发生竞争。
  • 互斥:每次只允许一个进程进入临界区,其他进程只能先等待。

在单机(单台服务器)环境下,各进程共享同一台机器的内存和时钟,常用信号量、互斥锁等机制即可实现。但在分布式系统中,进程分散在不同的网络节点上,通信必须经过网络,面临以下几个挑战:

  1. 互联网特性

    • 不同节点通过网络传输消息,会有网络延迟、丢包或乱序等不确定性。
    • 节点彼此没有共享内存,只能依赖消息传递来协调对临界资源的访问。
  2. 没有统一时钟

    • 各节点各自拥有物理时钟,难以做到完全同步。
    • 如果无法知道谁先谁后,就无法保证公平先来后到,需要引入逻辑时钟或其他机制来解决。
  3. 节点或网络故障

    • 某个节点或链路出现故障时,如何检测并进行故障恢复,才能保证系统依旧可用且不会长时间阻塞。

基于上述特性,分布式互斥算法应当满足:

  • 互斥性:任何时刻最多只有一个节点能够进入临界区。
  • 无饥饿(无饿死):每个请求都能在有限时间内得到满足,不会无限期等待。
  • 无死锁:避免多个节点相互等待对方持有的资源,从而导致整体停滞。
  • 公平性:先发出请求的节点应当先获得访问权,或至少不会被长期"插队"。

2. 分布式互斥算法分类

常见的分布式互斥算法可分为三大类(本文重点介绍以下三种):

  1. 集中互斥算法(Centralized Algorithm)

    • 核心思想:引入一个全局 "协调者(Coordinator)",所有节点的访问请求都交由它来排序和授权。
  2. 基于许可的互斥算法(Permission-Based Algorithm)

    • 核心思想:想要进入临界区的节点,需要向系统中其他节点请求许可(Permission),只有获得足够许可后才能访问。

    • 代表算法:

      • Lamport 算法
      • Richard & Agrawal 算法
  3. 令牌环互斥算法(Token Ring Algorithm)

    • 核心思想:在所有节点之间形成一个逻辑环,只有持有 "令牌(Token)" 的节点才能访问临界区,令牌在环上顺序传递。

下面分别详细讲解三种算法的工作原理、消息流程与优缺点。


3. 集中互斥算法

3.1 核心思路

  • 系统中预先选举出一个节点作为 "协调者(Coordinator)"。

  • 当某个进程(节点) Pi 想访问临界资源时,向协调者发送 REQUEST(Pi) 消息。

  • 协调者维护一个本地的"请求队列(按先后次序)"。

    • 若此时没有其他节点在使用资源,则直接向 Pi 发送 GRANT(Pi),Pi 收到后即可进入临界区。
    • 若已有其他节点持有该资源,则把 Pi 的请求追加到队列尾,等待前面的节点释放后再处理。
  • 当 Pi 使用完毕后,向协调者发送 RELEASE(Pi) ,协调者收到后,从请求队列中弹出下一个节点 Pj,并向其发送 GRANT(Pj),Pj 再进入临界区。

3.2 关键特性

  • 互斥保证:协调者一次只能向一个节点发放 GRANT,确保只有一个节点能够访问。
  • 先来后到:协调者将所有请求排队,根据请求到达时间顺序依次授权。
  • 避免饥饿:如果某节点长时间占用或宕机,协调者可检测超时并将其移出队列(或让其"自动释放"),从而让后续请求有机会执行。

3.3 消息交互流程(以两个节点 P1、P2 为例,详见图 3-1)

复制代码
图 3-1:集中互斥算法消息流程示意
     P1                 协调者               P2
(1) REQUEST(P1) ──────→ 
                    存入队列:[(P1, TS1)]
(2) GRANT(P1) ←──────    ←────── REQUEST(P2)
                    更新队列:[(P1, TS1), (P2, TS2)]
(3) P1 进入临界区
(4) RELEASE(P1) ──────→ 
                    更新队列:[(P2, TS2)]
(5) GRANT(P2) ←──────
(6) P2 进入临界区
(7) RELEASE(P2) ──────→ 空队列
  • 第 1 步:P1 向协调者发送 REQUEST(P1),时间戳记为 TS1。协调者将 (P1, TS1) 加入本地队列。
  • 第 2 步:发现队列仅有 P1,直接向 P1 返回 GRANT(P1)。此时 P1 进入临界区。
  • 第 3 步:P2 请求到达,发送 REQUEST(P2),时间戳记为 TS2(TS2 > TS1)。协调者将 (P2, TS2) 加入队列尾。
  • 第 4 步:P1 使用完成后,发送 RELEASE(P1)。协调者将 P1 从队列中移除,只剩 (P2, TS2),于是向 P2 发放 GRANT(P2)。
  • 第 5 步:P2 收到 GRANT 进入临界区,使用完后再通知 RELEASE(P2),队列空。

3.4 优缺点分析

  • 优点

    1. 实现简单,理解直观;
    2. 每次临界区访问仅需 3 次消息:REQUEST → GRANT → RELEASE。
  • 缺点

    1. 单点故障:协调者若发生故障,整个系统无法继续;
    2. 扩展性差:协调者会成为性能瓶颈,随着节点数增多,排队和消息处理压力变大;
    3. 协调者可能出现宕机或网络隔离,需要额外的故障检测与选举机制。

4. 基于许可的互斥算法

当系统规模扩大时,如果依赖单一协调者,瓶颈和单点故障问题更严重。基于许可的互斥算法没有集中节点,而是由各个节点之间互相"投票"或"许可"来决定谁先进入临界区。典型代表有 Lamport 算法和 Richard & Agrawal 算法,它们都基于逻辑时钟与请求队列来保证先来后到与互斥。

4.1 Lamport 算法

4.1.1 核心思想
  1. 逻辑时钟(Logical Clock)

    • 每个节点 Pi 维护一个本地逻辑时钟 Li,初始值为 0。
    • 当 Pi 发出临界区请求时,先执行 Li = Li + 1,并将新的 Li 作为时间戳 TSi 发送给其他节点。
    • 当 Pj 接收到一条请求消息(或 RELEASE)时,先将本地时钟更新为 Lj = max(Lj, TSi) + 1,再处理消息。
  2. 请求队列(Request Queue)

    • 每个节点维护一个本地请求队列,队列中元素为 (节点ID, 时间戳),按时间戳从小到大排序。如时间戳相等,则按照节点 ID 从小到大排序。

    • 当 Pi 发出 REQUEST(Pi, TSi) 时,会将自己这一请求加入本地队列,并向其它所有节点广播 REQUEST(Pi, TSi)。

    • 当 Pj(j ≠ i)收到 REQUEST(Pi, TSi) 后:

      1. 更新本地逻辑时钟:Lj = max(Lj, TSi) + 1
      2. (Pi, TSi) 插入本地请求队列;
      3. 向 Pi 发送一条 REPLY(Pj, Lj)(表明已许可)。
  3. 进入临界区的条件

    • Pi 只有在以下两种条件同时满足时,才可进入临界区:

      1. 本地队列中排在最前(时间戳最小且 ID 最小);
      2. 已收到来自所有其他节点的 REPLY 消息。
    • 当 Pi 进入临界区并使用完毕后,向所有节点广播 RELEASE(Pi, TSi')(TSi' = Li + 1),各节点收到后:

      1. 更新本地逻辑时钟:Lj = max(Lj, TSi') + 1
      2. 从本地请求队列中删除 (Pi, ...)

    这样,排在下一位的节点即可进入临界区。

4.1.2 消息流程示意(图 4-1)
复制代码
图 4-1:Lamport 算法消息交互(3 个节点 P1、P2、P3)
    P1                        P2                        P3
(1)L1= L1+1;TS1=1
     请求:REQUEST(P1,1) ──→
                            更新 L2=max(L2,1)+1=2
                            本地队列插入 (P1,1)
                          ←── REPLY(P2,2)
                            更新 L3=max(L3,1)+1=2
                            本地队列插入 (P1,1)
                          ←── REPLY(P3,2)

(2)P1 收齐 P2、P3 的 REPLY,且自己在 3 个节点队列中排第一
    → 进入临界区

(3)使用完毕:L1 = L1 + 1 = 2;广播 RELEASE(P1,2)
                            P2、P3 更新时钟并删除 (P1,1)

(4)此时本地队列中可能有 (P2,2)、(P3,2) 等请求
    根据时间戳和节点 ID 排序,让下一位进入
  • 消息数量:每次进入临界区需发送

    • n -- 1 条 REQUEST(广播给其它节点),
    • n -- 1 条 REPLY(每个节点一个),
    • n -- 1 条 RELEASE(广播)。
      共计 3(n − 1) 条消息。
4.1.3 特点与适用场景
  • 优点

    1. 无集中节点,无单点故障;
    2. 基于时间戳排序,保证先到先得,具有公平性;
    3. 不存在死锁或循环等待。
  • 缺点

    1. 通信开销较大:消息数随节点数线性增长,适合节点规模较小、临界资源请求频率相对较低的场景;
    2. 每个节点需维护本地请求队列,存储开销为 O(n)。

4.2 Richard & Agrawal 算法

4.2.1 算法思路

Richard & Agrawal 算法是在 Lamport 算法基础上的改进,目的是减少消息开销,将原本的 RELEASE 与 REPLY 合并,以降低通信量。主要思路如下:

  1. 发送请求

    • 当 Pi 想进入临界区时:

      1. 将本地逻辑时钟 Li = Li + 1,得到时间戳 TSi;
      2. (Pi, TSi) 插入本地请求队列;
      3. 向其它所有节点广播 REQUEST(Pi, TSi)
  2. 接收请求并判断

    • Pj(j ≠ i)收到 REQUEST(Pi, TSi) 后:

      1. 更新本地逻辑时钟 Lj = max(Lj, TSi) + 1

      2. 若 Pj 当前 没有正在等待或占用临界资源 ,则立即给 Pi 回复 REPLY(Pj, Lj)

      3. 若 Pj 正在等待或访问临界资源,则比较 (TSj, Pj)(TSi, Pi)

        • (TSi, Pi) 排在前面(时间戳更小或时间戳相同且 ID 较小),则等待,并暂时不回复;
        • 否则(即 Pj 的请求优先级更高),向 Pi 立刻回复 REPLY(Pj, Lj),但 Pj 本地继续排队。
      4. (Pi, TSi) 插入本地请求队列,按时戳排序。

  3. 进入临界区的条件

    • Pi 必须从所有 n -- 1 个节点都收到 REPLY,并且在自己本地队列中排在最前;
    • 方能进入临界区。
  4. 使用完成后

    • Pi 进入临界区使用完成后,将 (Pi, ...) 从本地队列中删除,并向本地队列(如果有)最前面的下一个节点发送 "伪 REPLY"(即代替 RELEASE 的作用),让该节点尝试进入。

    • 其余节点在收到 "伪 REPLY" 时,也会将 (Pi, ...) 从自己的本地队列中删除,并检查本地队列头部,如果自己排在最前且已收到所有许可,就可进入临界区。

4.2.2 消息流程示意(图 4-2)
复制代码
图 4-2:Richard & Agrawal 算法消息流程(3 个节点 P1、P2、P3)
    P1                        P2                        P3
(1)L1 = L1 + 1;TS1 = 1
     请求:REQUEST(P1,1) ──→ 
                            更新 L2 = max(L2,1)+1 = 2
                            本地队列插入 (P1,1)
                            P2 无本地请求 → 立即 REPLY(P2,2)
                          ←──
                            更新 L3 = max(L3,1)+1 = 2
                            本地队列插入 (P1,1)
                            P3 无本地请求 → 立即 REPLY(P3,2)
                          ←──

(2)P1 收到 P2、P3 的 REPLY,且本地队列头为 (P1,1)
    → 进入临界区

(3)使用完毕:删除本地队列中 (P1,1),向本地队列中后续进程 "发送 REPLY"  
    ------ 假设本地队列后续为 (P2,2),则发送 REPLY(P2, ...),允许 P2 进入。

(4)其余节点收到 P1 的删除信号后,也将 (P1,1) 从本地队列删除  
    若本地队列头为自己且已收到所有许可,则进入临界区。
4.2.3 特点与对比
  • 消息开销

    • 相比 Lamport 算法的 3(n − 1),Richard & Agrawal 算法每次只需 2(n − 1) 条消息:

      1. 一轮 REQUEST 给其他 n − 1 个节点;
      2. 收到的 n − 1 条 REPLY 即可进入;
      3. 使用完成后,节点会"顺便"触发对下一个队列头的 REPLY (合并了原来的 RELEASE);
  • 优点

    1. 与 Lamport 算法相比,消息量减少,通信效率更高;
    2. 依旧使用时间戳排序,保证先来后到,公平性较好;
  • 缺点

    1. 相对于集中式或令牌环算法,存储开销仍为 O(n);
    2. 需要维护本地请求队列、逻辑时钟,算法逻辑略微复杂;
  • 适用场景

    • 中等规模(节点数不太大)且资源竞争不太频繁的场景;
    • 希望在去中心化的同时尽量减少通信开销。

5. 令牌环互斥算法

5.1 核心思路

  • 系统中将所有节点按某种顺序构成一个逻辑环(Ring)。
  • 整个系统中仅保有一个 "令牌(Token)",它代表了访问临界区的唯一许可。
  • 只有持有令牌的节点才能进入临界区,使用完毕后必须将令牌传给环上的下一个节点。
  • 若节点收到令牌但不需要访问临界区,则直接将令牌传给下一个。

只要令牌在系统内部一直循环传递,就能保证:

  1. 互斥性:同时只有一个令牌,自然只有一个节点能进入临界区;
  2. 公平性:令牌按固定顺序依次传递,每个节点都能轮到;
  3. 消息开销可控:令牌在环上每传递一次,记为 1 条消息;若环上有 n 个节点,则令牌完整绕行一圈需 n 条消息,平均到每个请求上的消息开销约为 n/2。

5.2 消息流程示意(图 5-1)

复制代码
图 5-1:5 个节点构成的令牌环示意
   P1 → P2 → P3 → P4 → P5 → P1(循环)

(1)假设初始令牌在 P1,若 P1 需要访问:  
    P1 收到令牌 → 进入临界区 → 使用完毕 → 发送 TOKEN → P2  

(2)若 P2 在收到令牌时不需要访问,则直接 "转发" 令牌:  
    P2 将令牌发送给 P3 → ...  

(3)若 P3 需要访问,则:  
    P3 收到令牌 → 进入临界区 → 使用完毕 → 发送 TOKEN → P4

(4)依此循环,令牌永远在环上移动,每个节点最终都能获得令牌。
  • 消息数:每次令牌传递算作 1 条消息。若某个节点从请求到实际获得令牌需要等待 k 步,则消息数为 k (直到令牌传到为止)。

5.3 特点与缺点

  • 优点

    1. 无需广播,仅在相邻两节点之间传递令牌,通信开销较小;
    2. 保证了先来后到的"轮询"机制,公平性好;
    3. 不存在死锁:只要令牌不丢失,节点始终能轮到。
  • 缺点

    1. 令牌丢失风险:若持有令牌的节点宕机或网络隔离,令牌就会丢失,整个系统无法访问临界区,需额外机制检测并重建令牌;
    2. 环重构复杂:节点加入或退出都会破坏原有环,需要重新组织环拓扑,且重构过程中无法使用临界资源;
    3. 延迟受环大小影响:若环上节点数较多,则令牌传递到某一节点的延迟线性增大,不适合大规模场景;
  • 适用场景

    • 节点数量有限(小规模系统),资源访问频率高且使用时间短;
    • 网络相对可靠、节点变动较少的场景。

6. 三种算法对比小结

算法名称 消息开销 互斥保证 公平性 单点故障 扩展性 适用场景
集中互斥算法 3 条(REQUEST→GRANT→RELEASE) 绝对互斥 先来后到 存在单点故障 较差 小规模、对延迟要求不高的场景
Lamport 算法 3(n − 1) 条 无单点故障 严格基于时间戳 无单点故障 中等 节点数不多、资源请求较频繁的场景
Richard & Agrawal 算法 2(n − 1) 条 无单点故障 基于时间戳 无单点故障 中等 想在 Lamport 基础上减少消息量的场景
令牌环互斥算法 每次令牌传递 1 条 → 平均 n/2 条 无单点故障 (若令牌丢失则宕机) 轮询公平 令牌丢失视为故障 较差 (环受拓扑限制) 小规模、节点变动少、资源访问频繁且快的场景

6.1 选型建议

  1. 系统规模很小(节点数 ≤ 10)且对延迟要求不高

    • 可以考虑集中互斥算法:实现最简单,但要保证协调者高可用;
  2. 系统规模中等(节点数约 10 -- 50),去中心化,又希望保证较高公平性

    • 可优先选择 Richard & Agrawal 算法,由于消息量比 Lamport 算法少,且同样保证先来后到;
  3. 系统规模小且临界资源访问速度快、频率高

    • 令牌环互斥适合:令牌在环上快速循环,通信开销低,但必须保障令牌不会丢失,环重构开销也要评估;
  4. 系统需要高度容错、节点可能频繁增删

    • Lamport 算法更易于动态扩展,不用维护环结构,但通信成本更高;

7. 总结

  1. 分布式互斥算法的核心目标:在无共享内存、无全局时钟的分布式环境下,通过消息传递保证临界资源的互斥访问,避免并发冲突、死锁与饥饿。
  2. 集中互斥算法:思想最简单、通信量最少,但存在单点故障和可扩展性差的问题;
  3. 基于许可的互斥算法(Lamport、Richard & Agrawal):去中心化、没有单点故障,采用逻辑时钟保证先来后到,通信开销随节点数线性增加;
  4. 令牌环互斥算法:令牌唯一、通信开销低,但令牌丢失或环结构变更的容错难度大。

不同场景应根据节点规模、资源访问频率、容错需求等综合考量,选择最合适的分布式互斥算法。以上内容配以示意图,力求图文并茂、层次清晰,帮助您快速理解三类算法的工作原理和应用场景。

相关推荐
FakeOccupational10 分钟前
【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信拓扑与操作 BR/EDR(经典蓝牙)和 BLE
笔记·分布式·p2p
闪电麦坤9514 分钟前
数据结构:泰勒展开式:霍纳法则(Horner‘s Rule)
数据结构·算法
伤不起bb2 小时前
Kafka 消息队列
linux·运维·分布式·kafka
suuijbd2 小时前
个人总结八股文之-基础篇(持续更新)
算法
2401_881244402 小时前
斐波那契数列------矩阵幂法
线性代数·算法·矩阵
dddaidai1232 小时前
kafka入门学习
分布式·学习·kafka
机器学习与统计学3 小时前
阿里牛逼,又开源两个遥遥领先的模型(向量化、重排),知识库要翻天地覆了
算法
小河豚oO3 小时前
LeetCode刷题---贪心算法---944
算法·leetcode·贪心算法
【杨(_> <_)】3 小时前
信号处理分析工具——时频分析(一)
算法·matlab·信号处理
还不起来学习?3 小时前
常见算法题目5 -常见的排序算法
java·算法·排序算法