1. 程式人生 > >ceph儲存 ceph叢集Paxos演算法分析

ceph儲存 ceph叢集Paxos演算法分析

個人整理PAXOS演算法分析文件下載連線為:

一致性問題

如上圖所示,伺服器Ai(i=1,2,..5)組成儲存叢集,每份資料在5臺伺服器中各保留一個副本。當客戶端C1和C2同時修改儲存在叢集中的同一份資料時,由於網路延遲的存在無法保證兩個修改資料的請求到達每臺伺服器的先後順序。也就是說,可能出現C1的請求先到達A1伺服器,而C2的請求先到達A2伺服器的情況。這種情況下,在A1和A2伺服器上對資料的修改順序不同,而修改順序的不同直接導致了這兩個副本資料的不一致。

要保持副本一致只要保證在每臺伺服器上對資料修改的操作順序相同。保證修改操作的順序相同的最簡單方法是採用Primary-Secondary模式,所有對資料的修改操作都先發送到Primary主機,然後由Primary主機分發給Secondary主機,也就是說修改操作的順序由Primary主機決定。但這種方法存在單點故障問題。另一種方法是選舉。當一臺主機接收到修改資料的請求時並不直接修改資料,而是發起一次選舉,只有贏得選舉的請求才可以修改資料,並且修改操作在所有主機上進行。也就是說修改操作的順序由叢集中的所有主機共同決定。舉個例子,如果在A1接收到C1請求的同時,A2也接收到了C2的請求,這時它們同時發起選舉以執行自己的修改操作,選舉的結果只有一個請求被批准。假如C2請求得到批准,那麼所有主機上先執行C2請求。A1選舉失敗後重新發起選舉,在得到批准後,所有主機執行C1的請求。從而,通過兩輪選舉確定了C1和C2請求在所有主機上的執行順序。

上面的第二個方法的關鍵是如何由叢集中的所有節點共同選擇一個值。

選擇一個值(Value)

執行一次Paxos演算法只能批准(chonsen)一個值,否則,仍舊無法確定值的先後順序。
提案得到批准的條件是,存在一個多數派,它的每個成員都接受(Accept)這個提案。假定每個成員最多隻能接受一個提案,那麼如果不是多數派,就會出現一次Paxos演算法批准多個值的問題。例如,假設只要有2個節點接受提案就批准提案,當A1、A2同時提出提案,A1和A3接受A1的提案,A2和A4接受A2的提案,因為它們都滿足被批准的條件,所以A1和A2的提案都得到批准。 因為任意兩個多數派之間至少存在一個公共的成員,所以可以保證得到多數派支援的提案是唯一的。繼續上面的例子,假設A5接受了A1的提案,那麼就不能再接受A2的提案,從而A1的提案得到多數派的支援獲得批准。

P1.每個Acceptor必須接受它接收到的第一個提案
提出約束條件P1的原因是,我們希望在只有一個節點提出提案的情況下,這個提案可以得到批准。但P1會引入另外一個問題,就是可能無法形成多數派。例如,A1到A5每個節點在同一時刻提出提案,並且自己率先接受了提案。由於每個節點最多隻能接受一個提案,因此每個提案都只有一個節點接受,無法形成多數派。為了可以形成多數派,放寬條件,讓每個Acceptor節點可以接受多個提案。但這又會引入新問題,繼續前面的例子,如果每個節點接受了所有接收到的提案,那麼5個提案都會得到批准,從而又出現了執行一次演算法批准多個提案的問題。

P2. 如果一個值為v的提案被批准,那麼提案號更大的提案得到批准的條件是它的值也為v


為解決上述問題,提出了約束條件P2。在每個Acceptor可以接受多個提案的情況下,會導致多個提案得到批准。這是不允許的,但是如果每個被批准的提案的值都相同,那就相當於只批准了一個提案,這是可以接受的。我們直接對提案的結果進行約束,允許批准多個提案,但是這些提案的值必須相同,也就是必須同第一個被批准的提案的值相同。

P2a. 如果一個值為v的提案被批准,那麼Acceptor只能接受提案號更大並且值為v的提案
事實上,只要滿足P1和P2兩個約束條件,就能夠保證執行一次演算法只批准一個提案了。但P2直接對結果進行約束,可操作性差。為此,需要尋找一個比P2更強的約束P2a,只要滿足了P2a也就滿足了P2。因為每個提案都需要Acceptor接受後才能夠被批准,因此P2a對Acceptor進行約束,讓它只接受提案號更大並且值為v的提案。考慮到P1也是對Acceptor的約束,分析下這兩者有無衝突。假設在A1提出提案時,A2處於離線狀態,並且由於訊息丟失A3沒有接收到A1的提案。因此,A1的提案只被A1、A4和A5接受,但這已經形成多數派,所以A1的提案被批准。隨後,A2上線提出了提案號更大但值不同的提案,A3接收到A2的提案,並且這是它接收到的第一個提案,根據P1的約束,它應該無條件接受這個提案。但是,根據P2a約束,因為提案的值和已經被批准的提案的值不相同,所以不能接受A2的提案。P1和P2a出現了衝突。

P2b. 如果一個值為v的提案被批准,那麼Proposer只能提出提案號更大並且值為v的提案
上面導致P1和P2a衝突的根源是,A2上線後貿然提出了值和已經批准的提案的值不相同的提案。為此,我們對Proposer進行約束,讓它提出提案號更大的提案的值同已經被批准的提案的值相同。這樣就避免了P1和P1a的衝突。另一方面,因為所有被Acceptor接受的提案都是由Proposer提出的,所以滿足了P2b約束就滿足了P2a約束,滿足了P2a約束就滿足了P2約束。

P2c. 如果有編號為n值為v的提案,那麼存在一個多數派S,(a)要麼S中的成員都沒有接受過編號小於n的提案,(b)要麼S接受的所有編號小於n的提案中提案號最大的提案的值為v
感覺從P2b到P2c有點跳躍,不過還是可以證明只要滿足了P2c就能滿足P2b。即要證明:如果一個編號為m值為v的提案被批准(P2b的假設條件),在滿足P2c的條件下,那麼Proposer提出的編號為n(n>m)的提案的值為v。證明過程採用數學歸納法。

當n=m+1時,採用反證法。即假設Proposer提出的編號為n的提案的值不為v而為w,那麼必定會和P2c的條件相沖突。根據P2c,如果有編號為n值為w的提案,那麼存在一個多數派S1,(a)要麼S1中所有成員都沒有接受過編號小於n的提案,(b)要麼S1中所有接受到的編號小於n的提案中編號最大的提案的值為w。因為編號為m值為v的提案已經被批准,批准該提案的多數派C和S1之間至少存在一個公共成員,這個成員接受過編號為m值為n的提案,所以(a)不成立。因為小於n的最大編號是m,而編號m的提案的值為v不為w,所以(b)不成立。從而,假設不成立,即Proposer提出的編號為n的提案的值為v。

當n>m+1時,採用數學歸納法,即P2b在編號為m+1到n-1時都成立,要證明在編號為n時P2b依舊成立。證明過程仍然採用反證法。假設Proposer提出的編號為n的提案的值不為v而為w。那麼根據P2c,存在一個多數派S1,(a)要麼S1沒有接受過編號小於n的提案,(b)要麼S1接受的編號小於n的所有提案中編號最大的提案的值為w。因為編號為m值為v的提案被批准,而批准該提案的多數派C和S1之間存在至少一個公共成員,所以(a)不成立。另外,因為編號從m到n-1的提案的值都為v,所以(b)也不成立。因此假設不成立,P2c包含P2b的結論成立。

至此,我們只要滿足P1和P2c兩個約束條件,就可以達到執行一次演算法只批准一個提案(可以是多個提案,但提案的值相同)的目的了。滿足條件P1很容易,但為滿足P2c,每個Proposer提出編號為n的提案時都要先學習所有Acceptor已經接受的編號小於n且最大的提案的值,並將新提案的值設定成該提案的值。學習Acceptor已經接受的提案容易,但是要預測Acceptor將會接受的提案就比較麻煩。對此,Paxos使用了承諾策略。當Proposer向Acceptor獲取編號小於n的提案時,Acceptor一方面將自己接受的所有編號小於n的提案中編號最大的提案返回給Proposer,另一方面承諾不再接受編號小於n的提案。也就是說在Proposer學習過程中,Acceptor不會再接受編號小於n的提案,從而Proposer就不用去預測Acceptor在這段時間內會接受哪些編號小於n的提案了。

為滿足P2c約束,Proposer提出提案的過程分以下兩個步驟:
1、選擇一個新的提案號n,向所有Acceptor傳送Prepare請求,以獲取:(a) 讓Acceptor承諾不再接受編號小於n的提案;(b) Acceptor返回它接受的所有編號小於n的提案中編號最大的提案。
2、Proposer接受到所有Prepare請求的迴應後,提出編號為n值為v的提案。值v是所有Acceptor已經接受的所有編號小於n的提案中的編號最大的提案的值。如果所有的Acceptor都沒有接受過編號小於n的提案,那麼v的值可以由Proposer自己指定。

P1a. 如果Acceptor承諾過不再接受編號小於m(m>n)的提案,則拒絕接受編號為n的提案,否則,接受編號為n的提案
當Proposer提出編號為n的提案後,向Acceptor傳送Accept請求,以讓Acceptor接受自己的提案。Acceptor根據P1a的原則來決定是否接受提案。

演算法流程

為滿足P1a和P2c兩個約束,將演算法的執行流程分成兩個階段:
Phase1

(a) Proposer選擇一個提案號n,向所有的Acceptor傳送Prepare訊息;

(b) 如果Acceptor還沒有承若過不接受編號小於m(m>n)的提案,則承諾不再接受編號小於n的提案,並返回它已經接受的編號小於n的提案中編號最大的提案。

Phase2

(a) Proposer接受到所有Acceptor對Prepare請求的迴應後,提出編號為n值為v的提案。值v是所有Acceptor接受到的所有編號小於n的提案中編號最大的提案的值。如果沒有Acceptor接受過編號小於n的提案,則值v可由Proposer自己決定。提出提案後,向所有的Acceptor傳送Accept訊息,以期Acceptor接受提案。

(b) 如果Acceptor沒有向其它的Prepare請求承諾過不再接受編號小於m(m>n)的提案,則接受編號為n的提案。

活鎖問題

考慮這樣一種情況:
Ai(i=1,2,...,5)都沒有接受過任何提案,A1提出編號為n的Prepare請求併發送給其它節點,A2~A5節點接收到A1的Prepare請求後都承諾不再接受編號小於n的提案。但是,在A1還沒有發出Accept請求的時候,A2向所有節點發送了編號為n+1的Prepare請求,由於n+1大於n,所以所有節點都承諾不再接受編號小於n+1的提案。當A1提出的編號為n提案發送到各個節點時,每個節點都會拒絕接受編號為n的提案。同樣地,在A2的Accept請求還沒送到Ai節點時,Ai節點又接受到編號為n+2的Prepare請求並承諾不再接受編號小於n+2的提案,因此A2的提案又無法獲得批准。如此迴圈往復,始終無法批准提案。這就是活鎖問題。

產生活鎖問題的原因在於,無法控制Proposer提出提案的時機,在一輪演算法還沒執行結束時就提出提案導致前面的提案被撤銷。控制提案進度的方法是,選擇一個Leader節點,只允許Leader節點提出提案。Leader節點可以將提案儲存在佇列中,等一個提案批准後再從佇列中取出另外一個提案,這就避免了活鎖問題。

引入Leader後,Paxos演算法似乎變回到了Primary-Secondary模式,值的執行順序完全由Leader決定,並且Leader存在單點故障。但Paxos演算法的優勢在於無論是Leader、Acceptor宕機都能保證正常工作。 如果Leader宕機,那麼就要執行選舉演算法從現有的節點中選舉出一個Leader。新Leader將從其餘節點中收集資訊,恢復狀態。如果Acceptor宕機,只要能夠形成多數派,就可以保持演算法正常執行。

全域性唯一提案號

全域性唯一的提案號是影響Paxos演算法正常運轉的關鍵。如果兩個Proposer提出相同的提案號,並且該編號的提案得到批准,那麼在某些節點上更新這個值,在另外一些節點上更新那個值,就會出現副本不一致的問題。在單機上提出唯一的編號比較容易,只要依次遞增即可。但在不同的機器上提出不同的編號,就要開動腦筋了。

Ceph是這麼計算提案號的:
last_pn = (last_pn / 100 + 1)100 + rank
last_pn是Leader節點最近提出的提案號,rank是Leader節點的編號。也就是說,所有的提案號都是100的整數倍加上節點自己的編號。當rank值小於100時,可以保證每個節點提出的提案號都是全域性唯一的,具體程式碼參考Paxos::get_new_proposal_number()函式。

參考資料