一、分布式算法
1.1 導論
並行處理與分布式處理的區別:
- 並行處理目標:使用所有處理器執行一個大任務。
- 分布式處理具有更高程度的不確定性和行為的獨立性,每個處理器都有自己獨立的任務。
分布式系統作用:
- 共享資源
- 改善性能
- 提高可靠性
模型:
- 異步共享存儲模型:用於緊耦合機器
- 異步 msg 傳遞模型:用於松散耦合機器及廣域網
- 同步 msg 傳遞模型:msg 延遲上界已知,系統執行划分為輪執行,是異步系統的特例
1.2 消息傳遞中的基本算法
每個處理器\(p_i\)可以模型化為一個具有狀態集\(Q_i\)的狀態機
初始狀態:\(Q_i\)包含一個特殊的初始狀態子集,每個inbuf
必須為空,outbuf
不一定為空。
轉換函數:將可訪問狀態的inbuf
轉換到outbuf
配置:分布式系統上某點整個算法的全局狀態
事件:計算事件和傳遞事件
執行:配置和事件交錯的序列。
需滿足安全性條件和活躍性條件。
安全性:某個性質在每次執行的每個可達配置里都成立。“壞事從不發生”
活躍性:某個性質在每次執行的某些可達配置里成立。“最終某個好事發生”
滿足安全性條件稱為執行。同時滿足活躍性條件稱為容許執行。
1.2.1 轉移系統與安全性和活躍性
轉移系統:\(三元組 Triple \ S=(C,\rightarrow,I)\),\(C\)是配置集,\(I\)是初始配置的一個子集,\(\rightarrow\)是配置集上的二元轉移關系。
可達:\(\exist \ 序列 \gamma_0 \rightarrow \gamma_1 \rightarrow \cdots \rightarrow \gamma_k = \delta,且\forall 0 \le i \le k-1,\gamma_i \rightarrow \gamma_{i+1},則稱\delta是可達的\)
安全性:\(S=(C,\rightarrow,I),斷言/性質P總是成立\)
\(\{P\} \rightarrow \{Q\}:\forall \gamma \rightarrow \delta, if \ P(\gamma),then \ Q(\delta)\),即如果 P 在轉移前成立,則 Q 在轉移后成立
定義:\(P是S的不變式,\forall \gamma \in I,P(\gamma)成立且\{P\}\rightarrow\{P\},P總是成立\),P 為安全性條件
定理:如果 P 是 S 的不變式,那么對 S 的每次執行的每一配置 P 都成立
活躍性:在算法的每次執行的某些配置中,P 為真,且最終 P 為真。
判斷方法:
- 范函數
- 無死鎖 / 系統正常終止
范函數:\(轉移系統S和斷言P,f:配置集C\rightarrow良基集\omega\)
良基集:\(偏序集(\omega,<)是良基的,當且僅當不存在無窮遞減序列,即存在最小值\)
1.2.2 系統
異步系統
異步:msg 傳遞時間和一個處理器的兩個相繼步驟之間的時間無上界
執行片段:一個有限或無限的序列,由配置和事件交替組成
執行:從初始配置開始的執行片段
調度:執行中的事件序列
容許執行:某個處理器有無限個計算事件(指處理器沒有出錯,並不是無限步),每個發送的 msg 都最終被傳遞。
同步系統
處理器按輪執行,一輪里,每個處理器可以發送一個 msg 到鄰居,每個處理器一接受到 msg 就計算。
輪:配置和事件序列可以划分為不相交的輪,每輪由一個傳遞事件和一個計算事件組成。
同步系統與異步系統的區別:
- 無錯的同步系統中,算法的執行只取決於初始配置
- 異步系統中,算法可能有不同的執行(不代表不同的結果)
1.2.3 復雜性度量
消息復雜度:所有容許執行上發送消息總數的最大值
時間復雜度:
- 同步系統:最大輪數
- 異步系統:所有計時容許執行中直到終止的最大時間
1.2.4 生成樹上的廣播和斂播
生成樹 ST:具有共同頂點且不出現回路的子圖。
最小生成樹 MST:所有邊權值和最小的生成樹。
廣播
基本步驟:
- 根\(p_r\)發送\(M\)給所有的孩子。
- 某節點收到父節點的\(M\)時,發送\(M\)給自己的所有孩子。
upon receiving no msg:
if i=r then
send <M> to all children;
terminates;
upon receiving <M> from P_j:
send <M> to all children;
terminates;
消息復雜度\(O(n)\)
時間復雜度\(O(h),h為生成樹高度\)
斂播
基本步驟:
- 每個葉子節點發送消息給雙親
- 每個非葉節點等待所有孩子的消息,之后再發消息給雙親
消息復雜度\(O(n)\)
時間復雜度\(O(h),h為生成樹高度\)
1.2.5 構造生成樹
Flooding 算法
消息復雜度:\(O(2m-(n-1))\)
事件復雜度:\(O(D),D為網直徑\)
基本思想:
-
根節點發送消息\(M\)給所有鄰居
-
當\(P_i\)收到來自\(P_j\)的消息\(M\)且是第一次收到消息\(M\)時,\(P_i\)發送\(<parent>\)給\(P_j\),\(P_i\)向其他向自己發送消息\(M\)的節點發送\(<reject>\),\(P_i\)向\(P_j\)以外的鄰居發送消息\(M\)
-
\(P_i\)收到\(P_j的<parent>\)消息,則表明\(P_i是P_j的父節點\)
Code for Pi (0<=i<=n-1)
初值:parent=nil;集合children和other均為空集
upon receiving no message:
if i=r and parent=nil then { //根尚未發送M
send M to all neighbors;
parent:=i;} //根的雙親置為自己
upon receiving M from neighbor pj:
if parent=nil then { //pi此前未收到過M,M是pi收到的第1個msg
parent:=j;
send <parent> to pj; //pj是pi的雙親
send M to all neighbors except pj;
}else //pj不可能是pi的雙親,pi收到的M不是第1個msg
send <reject> to pj;
upon receiving <parent> from neighbor pj:
children:=children∪{ j }; //pj是pi的孩子,將j加入孩子集
if children∪other包含了除parent外的所有鄰居 then terminate;
upon receiving <reject> from neighbor pj:
other:=other∪{ j }; //將j加入other,通過非樹邊發送的msg。
if children∪other包含了除parent外的所有鄰居 then terminate
異步模型中,構造以\(p_r\)為根的生成樹
同步模型中,構造的一定是BFS。
在異步系統中無法生成 BFS,因為消息傳遞無上界,最壞可能出現單鏈。
1.2.6 指定根構造 DFS 生成樹
基本思想:
- 選定根節點\(P_r\),向某一鄰接節點發送消息\(M\)
- \(P_i\)收到\(P_j\)的消息\(M\)是第一個來自鄰接節點的消息時,認定\(P_j\)為其雙親,並向之后給自己發消息\(M\)的節點發送\(<reject>\)
- \(P_i\)向未發過消息的鄰居中任選一個發送消息\(M\),並等待回復
- \(P_i\)向所有鄰居發過消息,則\(P_i\)終止
Code for Pi (0<=i<=n-1)
初值:
parent = nil;
children = Φ;
unexplored = Pi's neighbors
upon receiving no message:
if i=r and parent=nil then { //當Pi為根且未發送M
parent:=i; // 將parent設置為自身
任意 Pj ∈ unexplored
將 Pj 從 unexplored 中刪去
send M to Pj;}//endif
upon receiving M from neighbor Pj:
if parent=nil then { //pi此前未收到過M
parent:=j;
將 Pj 從 unexplored 中刪去
if unexplored!=Φ then {
任意 Pk ∈ unexplored
將 Pk 從 unexplored 中刪去
send M to Pk;
}else send <parent> to parent;
}else send <reject> to pj; //當Pj已訪問過
upon receiving <parent> or <reject> from neighbor pj:
if received <parent> then add j to children; // Pj是Pi的孩子
if unexplored = Φ then { //Pi的鄰居均已訪問
if parent!=i then send <parent> to parent; //Pi非根,返回至雙親
terminate; //以Pi為根的DFS子樹已構造好
}else{ //選擇Pi未訪問過的鄰居訪問
任意 Pk ∈ unexplored
將 Pk 從 unexplored 中刪去
send M to Pk;
}
消息復雜度,時間復雜度:\(O(m)\)
1.2.7 不指定根構造 DFS 生成樹
與領導者選舉問題類似,為破對稱問題
基本思想:
-
每個結點均可自發喚醒,試圖構造一根以自己為根的 DFS 生成樹。若兩棵 DFS 樹試圖鏈接同一節點(未必同時),選擇根 ID 較大的 DFS 樹加入。
-
每個結點設置一個 leader 變量,即為當前 DFS 樹的根 ID
-
結點自發喚醒時,將自己的 leader 發送給某一鄰居
Code for Pi (0<=i<=n-1)
初值:
parent = nil;
leader = 0;
children = Φ;
unexplored = Pi's neighbors
upon receiving no message: // 自主喚醒
if parent=nil then { //若非空,則Pi在某子樹上,則Pi失去競選機會
leader:=id; parent:=i; // 將parent設置為自身
任意 Pj ∈ unexplored
將 Pj 從 unexplored 中刪去
send <leader> to Pj;}//endif
upon receiving <new_id> from neighbor Pj:
if leader<new_id then { //將Pi所在樹合並到Pj所在樹中
leader:=new_id; parent:=j;
unexplored:=all neighbors of Pi except Pj; //重置未訪問鄰居集
if unexplored!=Φ then { //原Pi所在DFS樹修改各自id
任意 Pk ∈ unexplored
將 Pk 從 unexplored 中刪去
send <leader> to Pk;
}else send <parent> to parent;
}else if leader=new_id then send <already> to Pj;
upon receiving <parent> or <already> from neighbor pj:
if received <parent> then add j to children;
if unexplored = Φ then { //Pi的鄰居均已訪問
if parent!=i then send <parent> to parent; //Pi非根,返回至雙親
else terminate as root of the DFS tree; //以Pi為根的DFS子樹已構造好
}else{ //選擇Pi未訪問過的鄰居訪問
任意 Pk ∈ unexplored
將 Pk 從 unexplored 中刪去
send <leader> to Pk;
}
一個具有 m 條邊,n 個節點的網絡,自發啟動的節點由 p 個,ID 值最大的啟動時間為 t。
消息復雜度:\(O(pn^2)\)
時間復雜度:\(O(t+m)\)
1.3 環上選舉算法
匿名的:環中處理器沒有唯一的標識符,每個處理器具有相同的狀態機。
一致性、均勻的:不知道處理器的數目
解釋一致性和非一致性:
算法一:向右轉發 n 步 msg,然后終止。 => non_uniform
算法二:向右轉發 msg,直到 msg 發送者收到 msg。 => uniform
在一個匿名的、一致性算法中,所有處理器只有一個狀態機。
在一個匿名的、非一致性算法中,每個 n 值對應一種狀態機。
- 同步環系統不存在匿名的、一致性的領導者選舉算法
- 異步環系統不存在匿名的領導者選舉算法
1.3.1 異步環
開調度:在算法 A 的一次調度中,有一條邊上無消息傳遞,則為開邊。
(開調度未必是容許的調度,是容許執行的一個有限前綴)
異步環上的選舉算法有消息復雜度下界\(O(nlg \ n)\)
\(O(n^2)的算法\)
基本思想:
- 每個處理器發送一個標識符 msg 給左鄰居,然后等着接收右鄰居的 msg。
- 每個處理器收到 msg 時,若收到的 id 大於自身,則向左鄰居轉發。
- 若處理器收到自己的標識符 id,則宣布自己是 leader,並發終止 msg 給左鄰居,然后終止
- 若處理器收到終止 msg,則向左轉發,然后終止
核心思想:處理器向左鄰居發送標識符 msg,通過比對 id 確定 leader,只有最大的 id 消息會回到他自己。
Code for Pi (0<=i<=n-1)
初值:asleep=true; id = i;
While (receiving no message) do
(1) if asleep do
(1.1) asleep = false
(1.2) send <id> to left-negihbor
end if
End while
While (receiving <i> from right-neighbor) do
(1) if id < <i> then send <i> to left-neighbor
end if
(2) if id = <i> then
(2.1) send <Leader,i> to left-neighbor
(2.2) terminates as Leader
end if
End while
While (receiving <Leader,j> from right-neighbor) do
(1) send <Leader,j> to left-neighbor
(2) terminates as non-Leader
End while
消息復雜度:\(O(n^2)\)
\(O(nlg(n))\)的算法
k 鄰居:據某一處理器距離不超過 k 的處理器,左邊有 k 個,右邊有 k 個,共有\(2k+1\)個。
基本思想:
- 階段 0:每個節點向兩個 1-鄰居發送 id 消息,若鄰居的 id 小於此消息則回復 reply,否則吞沒。若一個節點收到兩個鄰居的 reply,則自認為 leader,並進入階段 1。
- 階段\(l\):在上一階段成為 leader 的處理器,繼續發送 id 消息給\(2^l\)鄰居,若收到來自左右兩個方向的 reply,則自認為 leader。
- 若收到自己的消息,則終止。
核心思想:第\(l\)階段一個處理器試圖成為其\(2^l\)-鄰居的臨時 leader。只有在\(l-th\)階段成為 leader 的處理器才能繼續\((l+1)-th\)階段。
Code for Pi (0<=i<=n-1)
初值:asleep=true;
upon receiving no msg:
if asleep then{
asleep:=false;//每個結點喚醒后不再進入此代碼
send <probe,id, 0, 0> to left and right;
}
upon receiving <probe, j, l,d> from left_or_right (resp, right):
if(j=id) then //收到自己id終止,省略發終止msg
send <leader,id> to left neighbour;
terminate as the leader;
if(j>id) and (d<2^l) then //向前轉發probe msg
send <probe, j, l, d+1> to right_or_left (resp, left)
if(j>id) and (d≥2^l) then //到達最后一個鄰居仍未沒收
send <reply, j, l > to left_or_right (resp, right) // 回答
// 若j<id, 則沒收probe消息
upon receiving <reply ,j , l> from left (resp, right):
if j≠id then // 判斷是否轉發到初始點
send <reply, j, l> to right (resp, left); //轉發reply
else //j=id時,Pi已收到一個方向的回答msg
if already received <reply, j, l> from right (resp, left) then //也收到另一方向發回的reply
send <probe, id, l+1, 0> to left and right; //Pi是phase l的臨時leader,繼續下一階段
upon receiving <leader, idj> from right:
send <leader, idj> to left;
terminate as nonleader;
階段\(k\),臨時 leader 樹最多為\(\frac{n}{2^k+1}\),啟動的 msg 數目最多為\(4*2^k\)
消息復雜度:\(O(nlg \ n)\)
1.3.2 同步環
消息復雜度上界\(O(n)\),下界\(O(nlg \ n)\)
上界\(O(n)\):
- 非均勻:要求環中所有節點開始於同一輪
- 均勻:節點可以開始於不同輪
證明上界的非均勻算法
必須知道環大小 n,所有節點開始於同一輪
假定 id 最小的為 leader
基本思想:按階段運行,每個階段由 n 輪組成。階段\(i\)中,處理器 id 也為\(i\)的選舉為 leader,然后終止算法。
各節點互不知道彼此的 id 值。
消息復雜度:恰有 n 個消息被發送,\(O(n)\)
證明上界的均勻算法
無需知道環大小
一個處理可以:自發喚醒或被動喚醒
基本思想:
- 源於\(id=i\)節點的 msg,在被轉發前延遲\(2^i-1\)輪。
- 每個自發喚醒的節點繞環發送一個喚醒 msg,且無延遲
- 若節點在啟動前收到喚醒 msg,則該節點只作轉發操作。
只有自發喚醒的節點,才有可能選為 leader
初值:asleep=true;waiting = ф;R = 計算事件中接收msg的集合;s = ф;//the msg to be sent
if asleep then {
asleep:=false;
if R = Φ then { // pi未收到過msg,屬於自發喚醒
min:=id; // 參與選舉
s:=s+{<id>}; // 准備發送
}else{ //已收到過msg,但此前未啟動,被喚醒故Pi不參與
min:=∞; //選舉,置min為∞ ,使其變為relay結點
// relay:=true; ?
}
}
for each <m> in R do {// 處理完收到的m后相當於從R中刪去
if m < min then { // 收到的id較小時通過
become not elected; // Pi未被選中
// 可用relay控制使轉發節點不延遲?
將<m>加入waiting且記住m何時加入; // m加入延遲轉發
min:=m;
} // if m > min then it is swallowed
if m=id then become elected; // Pi被選中
} //endfor
for each <m> in waiting do
if <m> 是在2^m-1輪之前接收的 then
將<m>從waiting中刪去並加入S
send S to left;
發送的消息:
- 第一類:第一階段的 msg(喚醒 msg)
- 第二類:最終 leader 的 msg 進入自己的第二階段之前發送的第二階段 msg(其他節點發的)
- 第三類:最終 leader 的 msg 進入自己的第二階段之后發送的第二階段 msg(包括 leader 發的)
第一類總數最多\(n\)
第二類總數最多\(n\)
第三類總數最多\(2n\)
消息復雜度:\(O(4n)\)
有限制的下界\(O(nlg \ n)\)
序等價:兩個環\(x_0,x_1,\cdots,x_n-1\)和\(y_0,y_1,\cdots,y_n-1\)中\(x_i < x_j,當且僅當y_i<y_j\)
若一算法只與環上標識符之間的相對次序相關,而與具體 id 無關,則該算法一定只是基於標識符的比較
對於每個\(n\ge8,n是2的方冪\),存在大小為 n 的環\(S_n\),使得基於比較的同步 leader 選舉算法 A,在容許執行里發送的 msg 數目為\(O(nlg \ n)\)。
構造\(S_n\)
- 定義大小為 n 的環\(R_n^{rev}\),令\(P_i\)的 id 為\(rev(i):i的二進制逆序列\)
- 將環划分為長度為\(j,j為2的方冪\)的連續片段,則這些片段是序等價的。
- 片斷數為\(\frac{n}{j}\)
1.4 計算模型
計算模型的復雜性:
- 系統由並發部件構成
- 無全局時鍾
- 必須捕捉部件可能的失效
對策:
- 因果關系
- 一致狀態
- 全局狀態
為什么分布式系統缺乏全局系統狀態?
-
非即時通信(傳播延時,資源競爭,丟失 msg 重發)
-
相對性影響(大多數計算機的實際時鍾存在漂移,時鍾同步依舊是一個問題)
-
中斷(不可能在同一時刻觀察一個分布式系統的全局狀態)
次序:\(e_1 < e_2:事件e_1發生在e_2之前\)
定序:
- 發生在同一節點上的事件滿足全序,\(e_1<_pe_2\)
- \(e_1\)發送消息,\(e_2\)接收消息,則\(e_1<_me_2\)
Happens-before 關系:\(<_H\),一種偏序關系
並發事件:兩個事件不能由 \(<_H\) 定序
有時將 Happens-before 關系描述為一個有向無環圖
![]()
如何將 H 關系的偏序關系轉變成全序關系:
- 在有向無環圖 DAG 上的拓撲排序
- Lamport 算法
1.4.1 Lamport 時間戳
基本思想:
- 事件 e有附加時間戳:
e.TS
- 節點有局部時間戳:
my_TS
- msg有附加時間戳:
m.TS
- 節點執行事件時,將自己的時間戳賦給該事件。
- 節點發送 msg時,將自己的時間戳給所有的 msg。
- 接收消息時,本地時間戳更新為最大值
- 發送消息時,使用本地時間戳打標簽
Initially: my_TS = 0;
On event e:
if(e == 接收消息m) then
my_TS = max(m.TS, my_TS)); // 取msg時間戳和節點時間戳較大值
my_TS++;
e.TS = my_TS; // 給事件e打時間戳
if(e == 發送消息m) then
m.TS = my_TS; // 給消息打時間戳
每一時間的時間戳大於前驅時間的時間戳
問題:不同的時間可能有相同的時間戳(並發事件)
改進算法:使用節點地址作為時間戳低位,如\(1.1,2.2\)。標號后按字典序得到全序關系。
問題:無法通過時間戳判定兩個事件是否有因果關系
1.4.2 向量時間戳
向量時間戳 VT:\(e.VT是個數組,e.VT[i]=k表示再節點i上,事件e之前有k個事件(可包括自己)\)
基本思想:
-
節點有局部向量時間戳:
my_VT
-
事件 e 有向量時間戳:
e.VT
-
msg 有向量時間戳:
m.VT
Initially: my_VT=[0,0,,,0];
On event e:
if(e == 接收消息m) then
for i= 1 to M do // 向量時間戳的每個分量只增不減
my_VT[i] = max(m.VT[i], my_VT[i]);
my_VT[self]++; // self是本節點的名字
e.VT = my_VT; // 給事件e打時間戳
if(e == 發送消息m) then
m.VT = my_VT; // 給消息m打時間戳
向量時間戳比較:
e1.VT = (5,4,1,3) e2.VT = (3,6,4,2) e3.VT = (0,0,1,3)
e1 和 e2 之間沒有完全大於或完全小於的關系,則 e1 和 e2 是並發的。
e3 在因果序上先於 e1
1.4.3 因果通信
處理器不能選擇 msg 到達的時間,但是可以抑制過早到達的 msg。
如 TCP 中的 FIFO 通信。
基本思想:
- 抑制從 P 發送的消息,知道可以斷定沒有其他消息早於 m 發生
- 在每個節點上:
- \(earliest[1...M]:不同節點當前能夠傳遞的消息時間戳的下界\)
- \(blocked[1...M]P:阻塞隊列數組,每個分量是一個隊列\)
Causal Msg Delivery
Definition:
時間戳lk:
使用Lamport時間戳,lk=1;
使用向量時間戳,lk=(0,,,0,1,0,,,0),第k位為1;
Initially:
earliest[k] = lk,k=1,,,M // 不同節點當前能傳遞的消息時間戳的下界
blocked[k] = {},k=1,,,M // 阻塞隊列置空
On the receipt of msg <m> from node P:
delivery_list = {}
if (blocked[p]==空) then
earliest[p] = m.timestamp;
blocked[p].push_back(m); // 處理收到的消息
//處理阻塞隊列
while(∃k使blocked[k]非空 &&
對i=1,,,M(除k和self外) not_earliest(earliest[i],earliest[k],i) )
// 沒有消息比k更早到
{
將blocked[k]隊頭元素m’出隊,且加入到delivery_list;
if (blocked[k]!=空) then
earliest[k] = m’.timestamp;
else
increment earliest[k] by lk
}
deliver the msgs in delivery_list; // 按因果序
可能發生死鎖(一節點上時間不發送你要的 msg)
該因果通信算法常用於組播。