1. 程式人生 > 其它 >Joint Consensus兩階段成員變更的單步實現

Joint Consensus兩階段成員變更的單步實現

簡介:Raft提出的兩階段成員變更Joint Consensus是業界主流的成員變更方法,極大的推動了成員變更的工程應用。但Joint Consensus成員變更採用兩階段,一次變更需要提議兩條日誌, 在一些系統中直接使用時有些不便。那麼Joint Consensus成員變更能否只使用單步實現呢?

作者 | 祥光

來源 | 阿里技術公眾號

一 引言

分散式系統執行過程中節點經常會出現故障,需要支援節點的動態增加、刪除和替換。
成員變更是分散式系統繞不開的話題,特別是在一致性系統中,對於提升運維能力和服務可用性都有很大的幫助。

Raft提出的兩階段成員變更Joint Consensus是業界主流的成員變更方法,極大的推動了成員變更的工程應用。但Joint Consensus成員變更採用兩階段,一次變更需要提議兩條日誌, 在一些系統中直接使用時有些不便。雖然Raft也提出了單步成員變更方法,但單步成員變更方法一次只能增加或減少一個成員,限制較大,並且容易踩坑,一般不推薦使用。

那麼很自然的想到,Joint Consensus成員變更能否只使用單步實現呢?本文對這個問題進行了深入探討。

二 成員變更

我們先來回顧下一致性協議中的成員變更問題。成員變更是在叢集執行過程中改變執行一致性協議的節點,如增加、減少節點、節點替換等。成員變更過程不能影響系統的可用性。

成員變更也是一個一致性問題,即所有節點對成員配置達成一致。但是成員變更又有其特殊性,因為在成員變更的過程中,參與投票的成員會發生變化。

圖1 成員變更的某一時刻Cold和Cnew中同時存在兩個不相交的多數派

如果將成員變更當成一般的一致性問題,成員變更過程中,各節點從舊成員配置Cold切換到新成員配置Cnew的時刻可能有差異,可能在某一時刻Cold和Cnew中同時存在兩個不相交的多數派,形成雙Quorum,破壞一致性。

為了解決這個問題,Raft提出了兩階段的成員變更方法Joint Consensus。

1 Joint Consensus成員變更

Joint Consensus成員變更為了避免雙Quorum問題,引入一個聯合成員配置Cold,new作為過渡配置, Cold,new是Cold和Cnew的組合。Cold與Cold,new的Quorum有交集,Cold,new與Cnew的Quorum也有交集。成員變更先從Cold切換到Cold,new,待Cold,new提交後,再切換到Cnew,保證Cold與Cnew不同時使用,因而不會形成雙Quorum,保障安全性。

圖2 Cold與Cold, new與Cnew三者的Quorum集合之間的關係

Joint Consensus使用兩條日誌完成成員變更過程。Leader收到成員變更請求後,先向Cold和Cnew同步一條Cold,new日誌,此後所有日誌都需要Cold和Cnew兩個多數派的確認。Cold,new日誌在Cold和Cnew都達成多數派之後才能提交,此後Leader再向Cold和Cnew同步一條只包含Cnew的日誌,此後日誌只需要Cnew的多數派確認。Cnew日誌只需要在Cnew達成多數派即可提交,此時成員變更完成,不在Cnew中的成員自動下線。

圖3 Joint Consensus成員變更過程

成員變更過程中如果發生Failover,老Leader宕機,Cold,new中任意節點都可能成為新Leader,如果新Leader上沒有Cold,new日誌,則繼續使用Cold,Follower上如果有Cold,new日誌會被新Leader截斷,回退到Cold,成員變更失敗;如果新Leader上有Cold,new日誌,則繼續將未完成的成員變更流程走完。

2 單步成員變更

Joint Consensus成員變更之所以需要兩個階段,是因為對Cold與Cnew的關係沒有做任何假設,為了避免Cold和Cnew各自形成不相交的多數派而形成雙Quorum,才引入了兩階段方案。

如果增強成員變更的限制,假設Cold與Cnew的Quorum交集不為空,Cold與Cnew就無法形成雙Quorum,則成員變更就可以簡化為一階段。

實現單步的成員變更,關鍵在於限制Cold與Cnew,使Cold與Cnew的Quorum交集不為空。那麼怎麼樣限制Cold與Cnew,才能使Cold與Cnew的Quorum交集不為空呢?方法就是每次成員變更只允許增加或刪除一個成員。

圖4 增加或刪除一個成員時Cold與Cnew的Quorum

增加或刪除一個成員時的情形,如圖4所示,可以從數學上嚴格證明,只要每次只允許增加或刪除一個成員,Cold與Cnew不可能形成兩個不相交的Quorum。因此只要每次只增加或刪除一個成員,從Cold可直接切換到Cnew,無需過渡成員配置,實現單步成員變更。

單步成員變更一次只能變更一個成員,如果需要變更多個成員,如實現替換成員等,可以通過執行多次單步成員變更來實現。

單步成員變更理論雖然簡單,但卻埋了很多坑,實際用起來並不是那麼簡單。先前的文章Raft成員變更的工程實踐中有詳細介紹。

三 兩階段成員變更的單步實現

Joint Consensus成員變更雖然通用但是採用兩階段,一次變更需要提交兩條日誌,單步成員變更雖然只需要提交一條日誌,但是限制較大,一次只能變更一個成員。兩者的優勢能否結合呢?Joint Consensus成員變更能否只用單步實現呢?

Joint Consensus成員變更過程中,Cold,new日誌的提交已經讓各節點對Cnew配置達成了一致,那麼Cnew日誌有什麼作用呢?能否在Cold,new日誌提交後就從Cold,new配置切換到Cnew配置呢?這樣是不是就可以不需要Cnew日誌,變成單步實現了呢?

考慮Joint Consensus成員變更中Cnew日誌的作用,Cnew日誌在Cold,new日誌提交之後發起提議,節點收到並持久化Cnew日誌後從Cold,new配置切換到Cnew配置,不在Cnew配置中的成員在Cnew日誌提交後下線。根據這個過程,可以總結出Cnew日誌的作用:

  1. 通知節點在收到並持久化Cnew日誌後從Cold,new配置切換到Cnew配置。
  2. 通知不在Cnew配置中的節點在Cnew日誌提交後下線。
  3. 成員變更過程中發生Failover後,本地有Cnew日誌的節點具有優先選舉權。

如果能不使用Cnew日誌同時又完成Cnew日誌的工作,不就可以用單步實現兩階段的Joint Consensus成員變更嗎?事實上已經有系統探索過這條路。

1 ZooKeeper成員變更

ZooKeeper從3.5.0版本開始在Zab的基礎上支援了成員變更。ZooKeeper具有Primary Order特性,而使用兩條日誌的Joint Consensus成員變更無法保證Primary Order特性,為了既滿足成員變更的通用性,又不喪失Primary Order特性,ZooKeeper在論文《Dynamic Reconfiguration of Primary/Backup Clusters》中提出了自己的成員變更方法,並在ZooKeeper中應用了此方法,比Raft的提出還早。

如圖5是ZooKeeper成員變更協議,圖中舊成員配置用S表示,新成員配置用S‘表示,P為Leader節點,圖5展示了將B1和B2節點替換成B3和B4節點的過程:

圖5 ZooKeeper成員變更協議

初始化:為了讓新節點追上最新資料,新成員配置S’中的新節點B3、B4先連線到當前的主節點P,P會向它們傳輸自己當前的狀態作為他們的初始狀態。在Zab協議中當備節點連線上主節點時這樣的狀態傳輸就會自動發生,並且會繼續從主節點P接收所有後續的操作日誌(例如圖中的Op1和Op2),這個過程中節點B3、B4不參與投票。

步驟1:主節點P向連線到它的所有備節點(S U S‘)傳送成員變更日誌COP,COP日誌中攜帶舊成員配置S和新成員配置S‘,並等待舊成員配置S中的節點確認。一旦S中的多數派確認了COP日誌,就對S’達成了共識。

步驟2:在COP日誌之前的日誌只需要舊成員配置S中的多數派確認,可以在舊成員配置和新成員配置(S U S‘)中提交;在COP命令之後且在S’的啟用訊息ACTIVATE之前的日誌需要新舊成員配置(S U S‘)兩個多數派確認,並且只能在S’中提交;在S’的啟用訊息ACTIVATE後的日誌,只需要在S‘中確認和提交。

步驟3:主節點P等待COP日誌以及S'中COP之前的日誌的確認。

步驟4:一旦新舊成員配置(S U S1)兩個多數派都確認了COP日誌,主節點P就提交COP日誌,並廣播一條啟用訊息ACTIVATE來啟用新成員配置S’從而完成成員變更。與日誌同步訊息類似,ACTIVATE訊息包含主節點P的Epoch,攜帶過時的Epoch的ACTIVATE訊息將被忽略。

成員變更過程中如果發生Failover,可能出現下面幾種情況:

如果在COP日誌傳送之前Failover,那麼成員變更失敗,在舊成員配置中重新選主後繼續工作;

如果在COP日誌傳送之後並且在ACTIVATE之前Failover,新舊成員配置中任意節點都可能成為新Leader,如果新Leader上沒有COP日誌,則成員變更失敗;如果新Leader上有COP日誌,則繼續將未完成的成員變更流程走完。

如果在ACTIVATE後Failover,成員變更已經完成,但還無法保證新Leader一定在新成員配置中,此時不在新成員配置中的節點還不能下線。因此在傳送ACTIVATE訊息後還需要在新成員配置中提交一條no-op日誌,no-op日誌提交後可保證新Leader一定在新成員配置中,不在新成員配置中的節點可以安全下線。

ZooKeeper利用非同步的Commit訊息,也即ACTIVATE訊息來通知節點從新舊成員配置切換到新成員配置。使用非同步的no-op日誌讓不在新成員配置中的節點安全下線。ZooKeeper的ACTIVATE訊息和非同步的no-op日誌起到了Joint Consensus成員變更中Cnew日誌的作用。

2 改進的單步實現

ZooKeeper成員變更協議不如Joint Consensus成員變更那麼簡潔,Joint Consensus成員變更通過兩階段可以利用協議本身而不需要做過多的限制來保證成員變更的安全性。那麼ZooKeeper成員變更協議是否可以改進呢?

ZooKeeper成員變更協議中非同步的ACTIVATE訊息和no-op日誌其實就是為了完成Joint Consensus成員變更中Cnew日誌的作用,明白了這一點後那麼也可以將Joint Consensus成員變更的Cnew日誌改為非同步的,在Cold,new日誌提交後就認為成員變更完成,然後非同步的提交Cnew日誌。之所以可以將Cnew日誌改為非同步的,在Cold,new日誌提交後就認為成員變更完成,是因為Cold,new日誌一旦提交,各節點已經對新成員配置達成了一致,再也不會回退到舊成員配置了,剩下的過程最終一定會執行完成,Cnew日誌最終一定會提交。

還有一種改進方法是繼續保留ACTIVATE訊息,但不使用no-op日誌,那麼怎麼樣保證切換到新成員配置的節點具有優先選舉權呢?根據選舉的安全性,具有最新日誌的節點具有優先選舉權,那麼可以在選舉的時候攜帶節點當前的成員配置,在日誌一樣新的情況下,優先給已經切換到新成員配置的節點投票,即可保證切換到新成員配置的節點具有優先選舉權。新成員配置中的大多數節點切換到新成員配置後,不在新成員配置中的節點可以安全下線。

四 總結

Joint Consensus成員變更的提出極大的推動了成員變更的工程應用,其簡潔優美並且通用,但是採用兩階段,一次變更需要提交兩條日誌。本文探討了兩階段的Joint Consensus成員變更的單步實現方法,並做了一些改進,為成員變更的工程應用提供了更多的選擇。

原文連結
本文為阿里雲原創內容,未經允許不得轉載。