分布式计算
进程间通信
1. 全局状态的记录
全局状态包含各进程的本地状态和各信道的传输消息,获取全局状态的方法:
第一步:每个进程保存各自的本地状态
第二步:确定哪些消息应该纳入信道状态
2. 一致状态的判定
一个进程在记录自己的状态之后发送的消息,不能纳入全局状态包含的信道状态;该消息的接收操作,也不能纳入目标进程的本地状态
3. 通信原语的组合方式判定
同步/异步和阻塞/非阻塞的侧重点
-
同步/异步关键看数据是否真的发送出去或者接收到
-
同步:数据需要真的发送出去
-
异步:数据拷贝到内核缓冲区就行
-
-
阻塞/非阻塞关键看是否阻碍主进程的执行
-
阻塞:必须等到消息传递完成
-
非阻塞:消息发出后,由Wait函数后台监测执行情况
-
Send
- 同步阻塞、同步非阻塞、异步阻塞、异步非阻塞
Recv
-
同步阻塞和同步非阻塞
-
一般不定义异步的Recv,因为Recv必须等到数据到达
逻辑时钟与物理时钟同步
1. 向量时间的推进计算
每个进程 p i p_i pi维护一个向量 〖 v t 〗 i [ 1 ⋯ n ] 〖vt〗_i [1⋯n] 〖vt〗i[1⋯n],n为进程总数
-
向量 〖 v t 〗 i [ 1 ⋯ n ] 〖vt〗_i [1⋯n] 〖vt〗i[1⋯n]表示进程 p i p_i pi视图中的全局时钟
-
〖 v t 〗 i [ i ] 〖vt〗_i [i] 〖vt〗i[i]表示进程 p i p_i pi的本地时钟,即:本地时钟包含在全局时钟中
-
向量 〖 v t 〗 i [ j ] 〖vt〗_i [j] 〖vt〗i[j]表示进程 p i p_i pi所知道的有关进程 p j p_j pj本地时钟的最近信息
逻辑时间推进方法
-
当进程 p i p_i pi发生一个内部事件时,更新本地时钟 〖 v t 〗 i [ i ] = 〖 v t 〗 i [ i ] + 1 〖vt〗_i [i]= 〖vt〗_i [i]+1 〖vt〗i[i]=〖vt〗i[i]+1
-
消息发送:当进程 p i p_i pi发送一个消息时,将全局时钟 〖 v t 〗 i [ 1 ⋯ n ] 〖vt〗_i [1⋯n] 〖vt〗i[1⋯n]附加到消息中
-
消息接收:当进程 p i p_i pi接收到一个消息,且消息携带的逻辑时钟为vt
-
第一步: 1 ≤ k ≤ n : 〖 v t 〗 i [ k ] = m a x ( 〖 v t 〗 i [ k ] , v t [ k ] ) 1≤k≤n: 〖vt〗_i [k]=max(〖vt〗_i [k],vt[k]) 1≤k≤n:〖vt〗i[k]=max(〖vt〗i[k],vt[k]), 即:进程 p k p_k pk的逻辑时间在我不知情的时候推进了,现在通知我了,我需要更新关于 p k p_k pk的信息
-
第二步:更新本地逻辑时钟 〖 v t 〗 i [ i ] = 〖 v t 〗 i [ i ] + 1 〖vt〗_i [i]=〖vt〗_i [i]+1 〖vt〗i[i]=〖vt〗i[i]+1,本地发生一个接收事件
-
全局快照算法
1. Chandy-Lamport算法(FIFO通道)
发起者进程向自己的每个外出通道发送一个快照记录标志,每个接收到标志的进程分别执行标记发送规则和标记接收规则
标记发送规则
- 进程 p i p_i pi记录自己的本地状态
- 向每个外出通道发送“快照记录标志”
标记接收规则:进程 p j p_j pj在流入通道C收到Marker标志
-
如果进程 p j p_j pj还未记录自己的本地状态,则
- 记录通道C的消息状态为空
- 执行标记发送规则,将Marker标志扩散到其它进程
- 开始记录其它各流入通道的消息
-
否则(说明进程 p j p_j pj已经从其它流入通道收到了Marker标志)
- 将通道C上收集的消息作为该通道的状态,即:消息收集截止
2. Lai-Yang算法(非FIFO通道)
非FIFO通道的挑战
- 先于Marker标志发送的消息,有可能后于Marker标志到达
- 解决办法:明确地将PAST消息和FUTURE消息分成两类
算法原则
-
每个进程开始是白色,记录快照时变成红色,进程变红时执行标记发送规则,促使其它进程也变红
-
红色进程发送红色消息,白色进程发送白色消息
-
每个进程必须在接收到第一个红色消息之前记录本地状态
-
每个白色进程记录它沿着每个通道发送和接收的白色消息
-
通道状态记录: 〖 S C 〗 i j = p i 在 通 道 C i j 上 发 送 的 白 色 消 息 − p j 在 通 道 C i j 上 接 收 的 白 色 消 息 〖SC〗_{ij}=p_i 在通道C_{ij} 上发送的白色消息-p_j 在通道 C_{ij} 上接收的白色消息 〖SC〗ij=pi在通道Cij上发送的白色消息−pj在通道Cij上接收的白色消息
优缺点
-
消息明确的分成两类,其中红色消息是未来消息,不用考虑
- 因为通道状态只记录PAST发送,但还没收到的消息
-
缺点:需要记录大量的白色消息
基本图算法
1. 基于洪泛的异步单一启动者生成树算法
环境初始化,对于每个节点:
- 因为自己还没加入生成树中,所以还不知道父节点,因此, parent⟸⊥
- Children、Unrelated均为空
- 记录自己的所有邻居节点,准备发送消息
算法执行,对于每个节点:
- 如果是指定根节点,则启动QUERY洪泛
- 节点还没加入生成树,则确认加入生成树
- 所有邻居节点已经从不同路径加入生成树,终止
- 收到ACCEPT消息时,增加一个孩子节点
- 收到REJECT消息时,增加一个无关节点,因为该节点已经从其它路径加入了生成树
- 所有邻居节点处理完成时,算法终止
2. 不依赖于生成树的受限洪泛算法
算法意义
需要在一个图中广播消息,但不致引入过多的消息
核心思想
-
每个进程使用向量SEQNO[1⋯n],其中,SEQNO[k]记录本进程看到的由进程k发出的消息的最大编号
-
如果新到达的来自进程k的消息编号不大于SEQNO[k],则直接丢弃该消息;否则,将该消息Forward到本进程的每个出边上
-
“不大于”能够保证把冗余的消息排除掉,从而实现受限的洪泛
-
以上算法最终也生成一棵树
3. 全源最短路算法:异步Floyd-Warshall
环境初始化,对于当前进程i:
- LEN[1…n] 表示已知的i节点到j节点的最短路径长度
- PARENT[1…n] 表示当前节点i到其他节点的第一跳
消息类型的定义:
在分布式环境下,不可能掌握全局信息,在此情况下,如何根据s到pivot、以及pivot到t的距离,更新s到t的距离
- 需要将pivot进程维护的LEN向量变量广播到整个网络
- 消息类型
PIV_LEN(pivot,PIVOT_ROW[1…n])
用于这个向量的广播 - 消息类型
IN_TREE(pivot)
和NOT_IN_TREE(pivot)
用于标识广播消息的传递路径,广播消息沿着以pivot为根的树传播,IN_TREE(pivot)
标识本条边在广播树中,NOT_IN_TREE(pivot)
标识本条边不在广播树中
算法执行:
首先实现同步,然后传播pivot进程维护的LEN向量,最后更新路径
4. 最小权重生成树算法(同步)
Kruskal算法
- 单节点上的最小生成树算法
- 核心思想:将森林逐步合并成一棵树,森林的初始值就是图的顶点
- 在每次迭代中,找出连接两个不同单元的边,将两个单元合并成一个
- 最终将所有单元合并成一棵树
GHS算法
-
分布式系统中的最小生成树算法
-
以Kruskal算法为基础,因此能够充分利用并发性
-
主要流程,以下步骤循环logn次:
-
在整个森林中,每棵树有一个根节点,由它沿着树边发布一个广播消息,要求所有的节点都找出自己权重最小的出边
-
每个收到广播消息的节点检查自己的所有边<LocalID, RemoteID>
-
节点LocalID与RemoteID是否在同一颗子树上,是否同一个根?
-
找出不属于同一棵子树的最小出边
-
-
由叶子节点沿着生成树发起聚播操作,将最小权重边逐步上报到树根
-
树根收集所有信息,确定最小的权重边,再次发送广播消息
- 边<LocalID, RemoteID>加入生成子树
-
节点MAX(LocalID, RemoteID)发送广播消息,宣称自己为新的树根
- 有利于树的平衡
-
P2P系统中的查询算法
1. Chord分布式哈希表
P2P环境下的对象查找问题
-
每个存储在集群中的对象,都有一个经哈希生成的ID(如160位SHA1)
-
每个组成集群的服务节点,也有一个经哈希生成的ID
-
存储对象与服务节点共享一个哈希空间,因此,可以将存储对象放置在与其具有相近哈希值的服务节点上
-
各种算法的核心:怎么定义和查找相近的哈希值?
Chord的对象布局方法
- 对象的键值k被分配到满足以下条件的第一个节点
- 在ID形成的环中,节点的ID等于或者大于k,记节点ID为succ(k)
Chord的可扩展查找优化
- 每个节点不仅保存直接的后继节点,而是保存多个间接后继
- 节点i保存的数组为i.finger[x]=succ(i+2^(x-1)),x=1,2,3⋯
节点i加入ID环
-
节点i与已存在于环中节点j取得联系,j在环中确定i的位置
-
i的后继节点需要更新其前驱节点指针,使之指向节点i
-
i的前驱节点需要更细其后继节点指针,使之指向节点i
-
节点i创建其路由表
- 生成数组i.finger[x]=succ(i+2^(x-1)),x=1,2,3⋯
-
其它所有节点的路由表都可能更新,以显示i的存在
- 更新数组k.finger[x]=succ(k+2^(x-1)),x=1,2,3⋯
复杂度分析
-
对于有n个节点的Chord集群,查找的时间复杂度O(logn)
-
每个节点的路由表大小logn≤m,m表示ID的位数
最大独立集与选举算法
1. 最大独立集(Maximal Independent Set,MIS)
- 独立集为原集合的一个子集,该子集中任何两个顶点在原图中不相邻
算法思路
- 采用贪心算法,每当一个节点纳入最大独立集中时,将其邻居节点从原图中删除,因为这些节点不会再纳入最大独立集
- 重复以上过程,直到图中的顶点要么纳入最大独立集,要么被删除
算法分析
-
没有邻居节点,自己就是独立的
-
每个节点生成一个随机数,当自己生成的随机数小于所有邻居的随机数时,可以无二义性地将自己纳入独立集,否则,通知邻居节点,自己没有纳入独立集
-
如果一个邻居纳入独立集,自己需要被删除,并且通知其他邻居自己被删除了
-
否则,更新邻居节点,因为可能有邻居节点被删除
Gossip协议与事务提交
1. Gossip协议的优势分析
假定在每一轮中每个感染节点传播b个未感染节点,可简单认为 β = b ∕ n β=b∕n β=b∕n,因为对每个节点而言,被其他节点选中的概率就是 b ∕ n b∕n b∕n
-
低延迟:仅仅需要O(logn)轮的消息传递
-
鲁棒性:只有 1 / n c b − 2 1/n^{cb-2} 1/ncb−2个节点不会收到消息
-
轻量级:每个节点仅传送了cb∗logn次信息
-
容错性:50%的丢包率等价于使用b∕2代替b进行分析;50%的节点故障等价于使用 n ∕ 2 n∕2 n∕2代替n、同时使用 b ∕ 2 b∕2 b∕2代替b进行分析
因果序保证算法
1. Raynal-Schiper-Toueg算法
终止检测算法
1. 基于快照的终止检测算法
主要思路
-
在一个程序的所有进程中,肯定存在一个特定的进程,它是最后一个结束的
-
由于每个进程都不具备全局信息,当一个进程由活跃变为空闲时,假定自己就是最后一个结束的那个进程,向其它进程发送消息,收集全局快照
-
收集全局快照过程中,如果遇到还没结束的进程,则宣布本次快照收集过程失败
-
最后一个结束的进程能够成功收集到程序终止的全局快照
2. 基于生成树的终止检测算法
基本思想
-
所有进程根据关联关系组成图,在图中生成一棵树
-
树根向叶子节点发送广播消息
-
收到广播消息的叶子节点一旦变为空闲,向父节点报告
-
每个内部节点收到所有子节点空闲报告、且自己也变为空闲后,向父节点报告
-
根节点收到所有子节点报告后,宣布程序终止
分布式互斥算法
1. Lamport分布式互斥算法
请求临界区
-
进程 P i P_i Pi想进入临界区时,向其它所有进程发送 R E Q U E S T ( t i , i ) REQUEST(t_i,i) REQUEST(ti,i)广播消息,并将请求按时间戳顺序放到本地请求队列 〖 Q U E U E 〗 i 〖QUEUE〗_i 〖QUEUE〗i
-
进程 P j P_j Pj收到进程 P i P_i Pi的请求,将该请求按时间戳顺序放到本地队列 〖 Q U E U E 〗 j 〖QUEUE〗_j 〖QUEUE〗j,并向进程 P i P_i Pi发送返回消息 R E P L Y ( t j ) REPLY(t_j) REPLY(tj)
执行临界区:满足以下两个条件,进入临界区
-
进程 P i P_i Pi从所有其它进程收到时间戳大于 ( t i , i ) (t_i,i) (ti,i)的返回消息
-
进程 P i P_i Pi的请求处于本地队列 〖 Q U E U E 〗 i 〖QUEUE〗_i 〖QUEUE〗i的队首
释放临界区
-
进程 P i P_i Pi从临界区退出,从本地队列删除自己的请求,广播一个带时间戳的消息给其它所有进程
-
进程 P j P_j Pj收到进程 P i P_i Pi的释放消息,从自己的请求队列中删除进程 P i P_i Pi的请求
2. Ricart-Agrawala分布式互斥算法
主要思想
- 优化Lamport算法中的消息通信
请求临界区
-
进程 P i P_i Pi想进入临界区时,向其它所有进程发送 R E Q U E S T ( t i , i ) REQUEST(t_i,i) REQUEST(ti,i)广播消息
-
进程 P j P_j Pj收到进程 P i P_i Pi的请求
-
若进程 P j P_j Pj没有请求临界区、也没有正在执行临界区,则立即返回REPLY消息
-
若进程 P j P_j Pj正在请求临界区,但其时间戳大于进程 P i P_i Pi的请求,则立即返回REPLY消息
-
否则,进程 P j P_j Pj延迟返回REPLY消息,并设置 〖 R D 〗 j [ i ] = 1 〖RD〗_j [i]=1 〖RD〗j[i]=1,其中RD数组记录了所有被延迟发送返回消息的进程
-
执行临界区
- 收到所有进程的返回消息后,进程 P i P_i Pi进入临界区
释放临界区
- 进程
P
i
P_i
Pi退出临界区时,发出所有的延迟消息
- 对于任意的进程 P j P_j Pj ,如果 〖 R D 〗 i [ j ] = 1 〖RD〗_i [j]=1 〖RD〗i[j]=1,则向进程 P j P_j Pj发送返回消息,并设置 〖 R D 〗 i [ j ] = 0 〖RD〗_i [j]=0 〖RD〗i[j]=0
3. Singhal自适应分布式互斥算法
主要思想
- 如果少数进程频繁地执行互斥,其它进程执行互斥的频率很低,一个进程请求互斥时只需得到高频进程的许可就行
变量定义,每个进程保留以下局部变量
-
请求集合 R i R_i Ri :进程 S i S_i Si执行临界区之前,必须获得该集合中所有进程的许可
-
通知集合 I i I_i Ii :进程 S i S_i Si退出临界区之后,必须通知该集合中所有进程
-
逻辑时钟 C i C_i Ci :每个进程维护一个Lamport时钟
-
布尔变量
-
Requesting:当进程正在请求临界区时置为1
-
Executing :当进程正在执行临界区时置为1
-
My_priority :若进程正在执行临界区的请求比当前申请进入临界区的请求具有更高的优先级,则置为1
-
变量初始化
-
每个进程i的请求集合 R i R_i Ri 被初始化为所有进程号不大于i的进程
-
每个进程i的通知集合 I i I_i Ii 被初始化为其本身
REQUEST消息处理
- 更新自己的时钟
- 自己正在请求临界区,自己的优先级更高,将对方加入通知进程集合;自己的优先级更低,将对方加入请求进程集合
- 自己正在执行临界区,将对方加入通知进程集合
- 自己没有请求、也没有执行,将对方加入请求进程集合,自己下一步请求时,需要向对方发送请求
REPLY消息处理
收到一个返回消息,将对方从请求集合中删除