1. 程式人生 > >微信自研生產級paxos類庫PhxPaxos實現原理介紹

微信自研生產級paxos類庫PhxPaxos實現原理介紹

前言

本文是一篇無需任何分散式以及paxos演算法基礎的人可以看懂的。

標題主要有三個關鍵字,生產級,paxos,實現,涵蓋了本文的重點。什麼叫生產級,直譯就是能用於生產線的,而非實驗產品。生產級別擁有超高的穩定性,不錯的效能,能真正服務於使用者的。paxos就不用說了,而實現,是本文最大的重點,本文將避開paxos演算法理論與證明,直入實現細節,告訴大家一個生產級別的paxos庫背後的樣子。為何要寫這篇文章?paxos演算法理論與證明不是更重要麼?我幾年前曾經也讀過Paxos論文,雖然大致理解了演算法的過程,但是在腦海中卻無一個場景去構建這個演算法,而後也就慢慢印象淡化以至於最近重讀paxos論文的時候,感覺像是第一次讀論文的樣子。

在真正去實現了paxos之後,我才頓悟了這個問題。人們去理解一個理論或事務的時候,都會往這個理論或事物上套一個場景,然後在腦海裡模擬而試圖去懂得他。比如經典的Dijkstra演算法,事實它並不只用於最短尋路,然而在學習這個演算法的時候,最短尋路給我們提供了一個很好的場景,可以讓我們更快的去理解它。

第一次讀paxos論文,我不知道它的應用場景,不知道它是用來幹什麼的,也不知道它怎麼實現,然而這些往往就是一個基礎,大神可能可以自己創造場景,然而更多的人都需要知道這個場景,當你知道後,理解演算法將變得更為容易。

本文,將告訴你paxos是什麼,用來做什麼,怎麼使用它,如何工程化,如何做到生產級別,以及在工程上會遇到的問題與解決辦法。文中將用盡量講課方式的口吻,以及儘量避免專業術語,力求把這麼一件事能闡述的更為通俗和簡單易懂。

什麼是paxos

一致性協議

Paxos是一個一致性協議。什麼叫一致性?一致性有很多種,也從強到弱分了很多等級,如線性一致性,因果一致性,最終一致性等等。什麼是一致?這裡舉個例子,三臺機器,每臺機器的磁碟儲存為128個位元組,如果三臺機器這128個位元組資料都完全相同,那麼可以說這三臺機器是磁碟資料是一致的,更為抽象的說,就是多個副本確定同一個值,大家記錄下來同一個值,那麼就達到了一致性。

Paxos能達到什麼樣的一致性級別?這是一個較為複雜的問題。因為一致性往往不取決與客觀存在的事實,如3臺機器雖然擁有相同的資料,但是資料的寫入是一個過程,有時間的先後,而更多的一致性取決於觀察者,觀察者看到的並未是最終的資料。這裡就先不展開講,先暫且認為paxos保證了寫入的最終一致性。

為何說是一個協議而不是一個演算法,可以這麼理解,演算法是設計出來服務於這個協議的,如同法律是協議,那麼演算法就是各種機構的執行者,使得法律的約束能得到保證。

Paxos的協議其實很簡單,就三條規定,我認為這三條規定也是paxos最精髓的內容,各個執行者奮力的去保護這個協議,使得這個協議的約束生效,自然就得到了一致性。

分散式環境

為何要設計出這麼一套協議,其他協議不行麼。如最容易想到的,一個值A,往3臺機器都寫一次,這樣一套簡單的協議,能不能達到一致性的效果?這裡就涉及到另外一個概念,Paxos一致性協議是在特定的環境下才需要的,這個特定的環境稱為非同步通訊環境。而恰恰,幾乎所有的分散式環境都是非同步通訊環境,在計算機領域面對的問題,非常需要Paxos來解決。

非同步通訊環境指的是訊息在網路傳輸過程中,可能發生丟失,延遲,亂序現象。在這種環境下,上面提到的三寫協議就變得很雞肋了。訊息亂序是一個非常惡劣的問題,這個問題導致大部分協議在分散式環境下都無法保證一致性,而導致這個問題的根本原因是網路包無法控制超時,一個網路包可以在網路的各種裝置交換機等停留數天,甚至數週之久,而在這段時間內任意發出的一個包,都會跟之前發出的包產生亂序現象。無法控制超時的原因更多是因為時鐘的關係,各種裝置以及交換機時鐘都有可能錯亂,無法判斷一個包的真正到達時間。

非同步通訊環境並非只有paxos能解決一致性問題,經典的兩階段提交也能達到同樣的效果,但是分散式環境裡面,除了訊息網路傳輸的惡劣環境,還有另外一個讓人痛心疾首的,就是機器的當機,甚至永久失聯。在這種情況下,兩階段提交將無法完成一個一致性的寫入,而paxos,只要多數派機器存活就能完成寫入,並保證一致性。

至此,總結一下paxos就是一個在非同步通訊環境,並容忍在只有多數派機器存活的情況下,仍然能完成一個一致性寫入的協議。

提議者

前面講了這麼多都是協議協議,在分散式環境當中,協議作用就是每臺機器都要扮演一個角色,這個角色嚴格遵守這個協議去處理訊息。在paxos論文裡面這個角色稱之為Acceptor,這個很好理解。大家其實更關心另外一個問題,到底誰去發起寫入請求,論文裡面介紹發起寫入請求的角色為提議者,稱之為Proposer,Proposer也是嚴格遵守paxos協議,通過與各個Acceptor的協同工作,去完成一個值的寫入。在paxos裡面,Proposer和Acceptor是最重要的兩個角色。

Paxos是用來幹什麼的

確定一個值

既然說到寫入資料,到底怎麼去寫?寫一次還是寫多次,還是其他?這也是我一開始苦惱的問題,相信很多人都會很苦惱。

這裡先要明確一個問題,paxos到底在為誰服務?更確定來說是到底在為什麼資料服務?還是引上面的例子,paxos就是為這128個位元組的資料服務,paxos並不關心外面有多少個提議者,寫入了多少資料,寫入的資料是不是一樣的,paxos只會跟你說,我確定了一個值,當這個值被確定之後,也就是這128個位元組被確定了之後,無論外面寫入什麼,這個值都不會改變再改變了,而且三臺機確定的值肯定是一樣的。

說到這估計肯定會有人蒙逼了,說實話我當時也蒙逼了,我要實現一個儲存服務啊,我要寫入各種各樣的資料啊,你給我確定這麼一個值,能有啥用?但先拋開這些疑問,大家先要明確這麼一個概念,paxos就是用來確定一個值用的,而且大家這裡就先知道這麼個事情就可以了,具體paxos協議是怎樣的,怎麼通過協議裡面三條規定來獲得這樣的效果的,怎麼證明的等等理論上的東西,都推薦去大家去看看論文,但是先看完本文再看,會得到另外的效果。

如下圖,有三臺機器(後面為了簡化問題,不做特別說明都是以三臺機器作為講解例子),每臺機器上執行這Acceptor來遵守paxos協議,每臺機器的Acceptor為自己的一份Data資料服務,可以有任意多個Proposer。當paxos協議宣稱一個值被確定(Chosen)後,那麼Data資料就會被確定,並且永遠不會被改變。

這裡寫圖片描述

Proposer只需要與多數派的Acceptor互動,即可完成一個值的確定,但一旦這個值被確定下來後,無論Proposer再發起任何值的寫入,Data資料都不會再被修改。Chosen value即是被確定的值,永遠不會被修改。

確定多個值

對我們來說,確定一個值,並且當一個值確定後是永遠不能被修改的,很明顯這個應用價值是很低的。雖然我都甚至還不知道確定一個值能用來幹嘛,但如果我們能有辦法能確定很多個值,那肯定會比一個值有用得多。我們先來看下怎麼去確定多個值。

上文提到一個三個Acceptor和Proposer各自遵守paxos協議,協同工作最終完成一個值的確定。這裡先定義一個概念,Proposer,各個Acceptor,所服務的Data共同構成了一個大的集合,這個集合所執行的paxos演算法最終目標是確定一個值,我們這裡稱這個集合為一個paxos instance,即一個paxos例項。

一個例項可以確定一個值,那麼多個例項自然可以確定多個值,很簡單的模型就可以構建出來,只要我們同時執行著多個例項,那麼我們就能完成確定多個值的目標。

這裡強調一點,每個例項必須是完全獨立,互不干涉的。意思就是說Acceptor不能去修改其他例項的Data資料,Proposer同樣也不能跨越例項去與其他例項的Acceptor互動。

如下圖,三臺機器每臺機器執行兩個例項,每個例項獨立運作,最終會產生兩個確定的值。這裡兩個實際可以擴充套件成任意多個。
這裡寫圖片描述

至此,例項(Instance)以成為了我現在介紹paxos的一個基本單元,一個例項確定一個值,多個例項確定多個值,但各個例項獨立,互不干涉。

然而比較遺憾的一點,確定多個值,仍然對我們沒有太大的幫助,因為裡面最可恨的一點是,當一個值被確定後,就永遠無法被修改了,這是我們不能接受的。大部分的儲存服務可能都需要有一個修改的功能。

有序的確定多個值

我們需要轉換一下切入點,也許我們需要paxos確定的值,並不一定是我們真正看到的資料。我們觀察大部分儲存系統,如LevelDB,都是以AppendLog的形式,確定一個操作系列,而後需要恢復儲存的時候都可以通過這個操作系列來恢復,而這個操作系列,正是確定之後就永遠不會被修改的。到這已經很豁然開朗了,只要我們通過paxos完成一個多機一致的有序的操作系列,那麼通過這個操作系列的演進,可實現的東西就很有想象空間了,儲存服務必然不是問題。

如何利用paxos有序的確定多個值?上文我們知道可以通過執行多個例項來完成確定多個值,但為了達到順序的效果,需要加強一下約束。

首先給例項一個編號,定義為i,i從0開始,只增不減,由本機器生成,不依賴網路。其次,我們保證一臺機器任一時刻只能有一個例項在工作,這時候Proposer往該機器的寫請求都會被當前工作的例項受理。最後,當編號為i的例項獲知已經確定好一個值之後,這個例項將會被銷燬,進而產生一個編號為i+1的例項。

基於這三個約束,每臺機器的多個例項都是一個連續遞增編號的有序系列,而基於paxos的保證,同一個編號的例項,確定的值都是一致的,那麼三臺機都獲得了一個有序的多個值。

下面結合一個圖示來詳細說明一下這個運作過程以及存在什麼異常情況以及異常情況下的處理方式。

這裡寫圖片描述

圖中A,B,C代表三個機器,紅色代表已經被銷燬的例項,根據上文約束,最大的例項就是當前正在工作的例項。A機器當前工作的例項編號是6,B機是5,而C機是3。為何會出現這種工作例項不一樣的情況?首先解釋一下C機的情況,由於paxos只要求多數派存活即可完成一個值的確定,所以假設C出現當機或者訊息丟失延遲等,都會使得自己不知道3-5編號的例項已經被確定好值了。而B機比A機落後一個例項,是因為B機剛剛參與完成例項5的值的確定,但是他並不知道這個值被確定了。上面的情況與其說是異常情況,也可以說是正常的情況,因為在分散式環境,發生這種事情是很正常的。

下面分析一下基於圖示狀態的對於C機的寫入是如何工作的。C機例項3處理一個新的寫入,根據paxos協議的保證,由於例項3已經確定好一個值了,所以無論寫入什麼值,都不會改變原來的值,所以這時候C機例項3發起一輪paxos演算法的時候就可以獲知例項3真正確定的值,從而跳到例項4。但在工程實現上這個事情可以更為簡化,上文提到,各個例項是獨立,互不干涉的,也就是A機的例項6,B機的例項5都不會去理會C機例項3發出的訊息,那麼C機例項3這個寫入是無法得到多數派響應的,自然無法寫入成功。

再分析一下A機的寫入,同樣例項6無法獲得多數派的響應,同樣無法寫入成功。同樣假如B機例項5有寫入,也是寫入失敗的結果,那如何使得能繼續寫入,例項編號能繼續增長呢?這裡引出下一個章節。

例項的對齊(Learn)

上文說到每個例項裡面都有一個Acceptor的角色,這裡再增加一個角色稱之為Learner,顧名思義就是找別人學習,她回去詢問別的機器的相同編號的例項,如果這個例項已經被銷燬了,那說明值已經確定好了,直接把這個值拉回來寫到當前例項裡面,然後編號增長跳到下一個例項再繼續詢問,如此反覆,直到當前例項編號增長到與其他機器一致。

由於約束裡面保證僅當一個例項獲知到一個確定的值之後,才能編號增長開始新的例項,那麼換句話說,只要編號比當前工作例項小的例項(已銷燬的),他的值都是已經確定好的。所以這些值並不需要再通過paxos來確定了,而是直接由Learner直接學習得到即可。

這裡寫圖片描述

如上圖,B機的例項5是直接由Learner從A機學到的,而C機的例項3-5都是從B機學到的,這樣大家就全部走到了例項6,這時候例項6接受的寫請求就能繼續工作下去。

如何應用paxos

狀態機

一個有序的確定的值,也就是日誌,可以通過定義日誌的語義進行重放的操作,那麼這個日誌是怎麼跟paxos結合起來的呢?我們利用paxos確定有序的多個值這個特點,再加上這裡引入的一個狀態機的概念,結合起來實現一個真正有工程意義的系統。

狀態機這個名詞大家都不陌生,一個狀態機必然涉及到一個狀態轉移,而paxos的每個例項,就是狀態轉移的輸入,由於每臺機器的例項編號都是連續有序增長的,而每個例項確定的值是一樣的,那麼可以保證的是,各臺機器的狀態機輸入是完全一致的。根據狀態機的理論,只要初始狀態一致,輸入一致,那麼引出的最終狀態也是一致的。而這個狀態,是有無限的想象空間,你可以用來實現非常多的東西。

如下圖這個例子是一個狀態機結合paxos實現了一個具有多機一致的KV系統。

這裡寫圖片描述

例項0-3的值都已經被確定,通過這4個值最終引出(b, ‘jeremy’)這個狀態,而各臺機器例項系列都是一致的,所以大家的狀態都一樣,雖然引出狀態的時間有先後,但確定的例項系列確定的值引出確定的狀態。

下圖例子告訴大家Proposer,Acceptor,Learner,State machine是如何協同工作的。

這裡寫圖片描述

一個請求發給Proposer,Proposer與相同例項編號為x的Acceptor協同工作,共同完成一值的確定,之後將這個值作為狀態機的輸入,產生狀態轉移,最終返回狀態轉移結果給發起請求者。

工程化

我們需要多個角色儘量在一起

上文提到一個例項,需要有Proposer和Acceptor兩個角色協同工作,另外還要加以Learner進行輔助,到了應用方面又加入了State machine,這裡面勢必會有很多狀態需要共享。如一個Proposer必須於Acceptor處於相同的例項才能工作,那麼Proposer也就必須知道當前工作的例項是什麼,又如State machine必須知道例項的chosen value是啥,而chosen value是儲存於Acceptor管理的Data資料中的。在概念上,這些角色可以通過任意的通訊方式進行狀態共享,但真正去實現,我們都會盡量基於簡單,高效能出發,一般我們都會將這些角色同時融合在一個機器,一個程序裡面。

下圖例子是一個工程上比較常規的實現方式。

這裡寫圖片描述

這裡提出一個新的概念,這裡三臺機器,每臺機器執行著相同的例項i,例項裡整合了Acceptor,Proposer,Learner,State machine四個角色,三臺機器的相同編號例項共同構成了一個paxos group的概念,一個請求只需要灌進paxos group裡面就可以了,跟根據paxos的特點,paxos group可以將這個請求可以隨意寫往任意一個Proposer,由Proposer來進行提交。Paxos group是一個虛設的概念,只是為了方便解釋,事實上是請求隨意丟到三臺機任意一個Proposer就可以了。

那麼具體這四個角色是如何工作的呢。首先,由於Acceptor和Proposer在同一個程序裡面,那麼保證他們處於同一個例項是很簡單的事情,其次,當一個值被確認之後,也可以很方便的傳送給State machine去進行狀態的轉移,最後當出現異常狀態,例項落後或者收不到其他機器的迴應,剩下的事情就交給Learner去解決,就這樣一整合,一下事情就變得簡單了。

我們需要嚴格的落盤

Paxos協議的運作工程需要做出很多保證(Promise),這個意思是我保證了在相同的條件下我一定會做出相同的處理,如何能完成這些保證?眾所周知,在計算機裡面,一個執行緒,程序,甚至機器都可能隨時掛掉,而當他再次啟動的時候,磁碟是他恢復記憶的方法,在paxos協議運作裡面也一樣,磁碟是她記錄下這些保證條目的介質。

而一般的磁碟寫入是有緩衝區的,當機器當機,這些緩衝區仍然未刷到磁碟,那麼就會丟失部分資料,導致保證失效,所以在paxos做出這些保證的時候,落盤一定要非常嚴格,嚴格的意思是當作業系統告訴我寫盤成功,那麼無論任何情況都不會丟失。這個我們一般使用fsync來解決問題,也就是每次進行寫盤都要附加一個fsync進行保證。

Fsync是一個非常重的操作,也因為這個,paxos最大的瓶頸也是在寫盤上,在工程上,我們需要儘量通過各種手段,去減少paxos演算法所需要的寫盤次數。

萬一磁碟fsync之後,仍然丟失或者資料錯亂怎麼辦?這個稱之為拜占庭問題,工程上需要一系列的措施檢測出這些拜占庭錯誤,然後選擇性的進行資料回滾或者直接丟棄。

我們需要一個Leader

由於看這篇文章的讀者未必知道paxos理論上是如何去確定一個值的,這裡簡單說明一下,paxos一個例項,支援任意多個Proposer同時進行寫入,但是最終確定出來一個相同的值,裡面是運用了一些類似鎖的方法來解決衝突的,而越多的Proposer進行同時寫入,衝突的劇烈程度會更高,雖然完全不妨礙最終會確定一個值,但是效能上是比較差的。所以這裡需要引入一個Leader的概念。

Leader就是領導者的意思,顧名思義我們希望有一個Proposer的領導者,優先由他來進行寫入,那麼當在只有一個Proposer在進行寫入的情況下,衝突的概率是極小的,這樣效能會得到一個飛躍。這裡再次重申一下,Leader的引入,不是為了解決一致性問題,而是為了解決效能問題。

由於Leader解決的是效能問題而非一致性問題,即使Leader出錯也不會妨礙正確性,所以我們只需要保證大部分情況下只有一個Proposer在工作就行了,而不用去保證絕對的不允許出現兩個Proposer或以上同時工作,那麼這個通過一些簡單的心跳以及租約就可以做到,實現也是非常簡單,這裡就不展開解釋。

我們需要狀態機記錄下來輸入過的最大例項編號

狀態機可以是任何東西,可以是kv,可以是mysql的binlog,在paxos例項執行時,我們可以保證時刻與狀態機同步,這裡同步的意思是指狀態機輸入到的例項的最大編號和paxos運行當中認為已經確認好值的例項最大編號是一樣的,因為當一個例項已經完成值的確認之後,我們必須確保已經輸入到狀態機並且進行了狀態轉移,之後我們才能開啟新的例項。但,當機器重啟或者程序重啟之後,狀態機的資料可能會由於自身實現問題,或者磁碟資料丟失而導致回滾,這個我們沒辦法像上文提到的fsync一樣進行這麼強的約束,所以提出了一種方法,狀態機必須嚴格的記得自己輸入過的最大例項編號。

這個記錄有什麼用?在每次啟動的時候,狀態機告訴paxos最大的例項編號x,而paxos發現自己最大的已確定值的例項編號是y,而x < y. 那這時候怎麼辦,只要我們有(x, y]的chosen value,我們重新把這些value一個一個輸入到狀態機,那麼狀態機的狀態就會更新到y了,這個我們稱之為啟動重放。

這樣對狀態機的要求將盡量簡單,只需要嚴格的記錄好這麼一個編號就可以了。當然不記錄,每次從0開始也可以,但這樣paxos需要從0開始重放,是一個蠢方法。

非同步訊息處理模型

上文說到分散式環境是一個非同步通訊環境,而paxos解決了基於這種環境下的一致性問題,那麼一個顯而易見的特點就是我們不知道也不確定訊息何時到達,是否有序到達,是否到達,我們只需要去遵守paxos協議,嚴格的處理每一條到達的訊息即可,這跟RPC模型比較不一樣,paxos的特點是有去無回。

這裡先定義一個名詞叫paxos訊息,這裡指的是paxos為了去確定一個值,演算法執行過程中需要的通訊產生的訊息。下圖通過一個非同步訊息處理模型去構建一個響應paxos訊息系統,從而完成paxos系統的搭建。

這裡寫圖片描述

這裡分為四個部分:
1. Request,即外部請求,這個請求直接輸入到Proposer裡面,由Proposer嘗試完成一個值的確定。
2. Network i/o,網路i/o處理,負責paxos內部產生的訊息的傳送與接收,並且只處理paxos訊息,採用私有埠,純非同步,各臺機器之前的network i/o模組互相通訊。
3. Acceptor,Proposer,Learner。用於響應並處理paxos訊息。
4. State machine,狀態機,例項確定的值(chosen value)的應用者。

工作流程:
1. 收到Request,由Proposer處理,如需要傳送paxos訊息,則通過network i/o傳送。
2. Net work i/o收到paxos訊息,根據訊息型別選擇Acceptor,Proposer,或Leaner處理,如處理後需要傳送paxos訊息,則通過network i/o傳送。
3. Proposer通過paxos訊息獲知chosen value,則輸入value到State machine完成狀態轉移,最終通知Request轉移結果,完成一個請求的處理。
4. 當paxos完成一個值的確認之後,所有當前例項相關角色狀態進行清空並初始化進行下一個編號的例項。

生產級的paxos庫

RTT與寫盤次數的優化

雖然經過我們在工程化上做的諸多要求,我們可以實現出一個基於paxos搭建的,可掛載任意狀態機,並且能穩定執行的系統,但效能遠遠不夠。在效能方面需要進行優化,方能上崗。由於上文並未對paxos理論做介紹,這裡大概說明一下樸素的paxos演算法,確定一個值,在無衝突的情況下,需要兩個RTT,以及每臺機器的三次寫盤。這個效能想象一下在我們線上服務是非常慘烈的。為了達到生產級,最終我們將這個優化成了一個RTT以及每臺機器的一次寫盤。(2,3)優化到(1,1),使得我們能真正在線上站穩腳跟。但由於本文的重點仍然不在理論,這裡具體優化手段就暫不多做解釋。

同時執行多個paxos group

由於我們例項執行的方式是確保i例項的銷燬才能執行i+1例項,那麼這個請求的執行明顯是一個序列的過程,這樣對cpu的利用是比較低的,我們得想辦法將cpu利用率提升上來。

一個paxos group可以完成一個狀態機的輸入,但如果我們一臺機器同時有多個狀態機呢?比如我們可以同時利用paxos實現兩種業務,每個業務對應一個狀態機,互不關聯。那麼一個paxos group分配一個埠,我們即可在一臺機器上執行多個paxos group,各自埠不同,互相獨立。那麼cpu利用率將能大幅提升。

比如我們想實現一個分散式的kv,那麼對於一臺機器服務的key段,我們可以再在裡面分割成多個key段,那每個小key段就是一個獨立的狀態機,每個狀態機搭配一個獨立paxos group即可完成同時執行。

但一臺機器搞幾十個,幾百個埠也是比較齷齪的手法,所以我們在生產級的paxos庫上,實現了基於一個network i/o搭配多組paxos group的結構。

這裡寫圖片描述

如上圖,每個group裡面都有完整的paxos邏輯,只需要給paxos訊息增加一個group的標識,通過network i/o的處理,將不同group的訊息輸送到對應的group裡面處理。這樣我們一臺機器只需要一個私有埠,即可完成多個狀態機的並行處理。

至此我們可以獲得一個多個paxos group的系統,完整結構如下:

這裡寫圖片描述

更快的對齊資料

上文說到當各臺機器的當前執行例項編號不一致的時候,就需要Learner介入工作來對齊資料了。Learner通過其他機器拉取到當前例項的chosen value,從而跳轉到下一編號的例項,如此反覆最終將自己的例項編號更新到與其他機器一致。那麼這裡學習一個例項的網路延時代價是一個RTT。可能這個延遲看起來還不錯,但是當新的資料仍然通過一個RTT的代價不斷寫入的時候,而落後的機器仍然以一個RTT來進行學習,這樣會出現很難追上的情況。

這裡需要改進,我們可以提前獲取差距,批量打包進行學習,比如A機器Learner記錄當前例項編號是x,B機器是y,而x < y,那麼B機器通過通訊獲取這個差距,將(x,y]的chosen value一起打包傳送給A機器,A機器進行批量的學習。這是一個很不錯的方法。

但仍然不夠快,當落後的資料極大,B機器傳送資料需要的網路耗時也將變大,那麼傳送資料的過程中,A機器處於一種空閒狀態,由於paxos另外一個瓶頸在於寫盤,如果不能利用這段時間來進行寫盤,那效能仍然堪憂。我們參考流式傳輸,採用類似的方法實現Learner的邊發邊學,B機器源源不斷的往A機器輸送資料,而A機器只需要收到一個例項最小單元的包體,即可立即解開進行學習並完成寫盤。

具體的實現大概是先進行一對一的協商,建立一個Session通道,在Session通道里直接採用直塞的方式無腦傳送資料。當然也不是完全的無腦,Session通過心跳機制進行維護,一旦Session斷開即停止傳送。

如何刪除Paxos資料

Paxos資料,即通過paxos確認下來的有序的多個值,後面我們稱之這個為paxos log,這些log作為狀態機的輸入,是一個源源不斷的。狀態機的狀態是有限的,但輸入是無限的,但磁碟的空間又是有限的,所以輸入必然不能長期保留,我們必須找到方法來把它刪除掉。

上文說到我們要求狀態機記錄下來輸入過的最大例項編號,這裡定義為Imax,那麼每次啟動的時候是從這個編號後開始重放paxos log,也就是說小於等於這個編號Imax資料是沒用的了,它不會再次使用,可以直接刪除掉。但這個想法不夠周全,因為paxos是允許少於多數派的機器掛掉的,這個掛掉可能是機器永遠離線。而這種情況我們一般是用一臺新的機器代替。這臺新的機器要幹什麼?他要從0開始重放paxos log,而這些paxos log從哪裡來?肯定是Learner找別的機器拷貝過來的。那別的機器刪了怎麼辦?涼拌。

但也並不是沒辦法了,我可以把這臺機狀態機相關的資料全部拷貝到新機,然後就可以從Imax來啟動了,那麼自然就不需要[0,Imax]的paxos log了。但是狀態機的資料是無時無刻不在寫入的,一個正在寫入的資料去拷貝出來,出現什麼情況都是不可預期的,所以這個方法並不能簡單的實現,什麼?停機拷資料?別逗了。但這個思路給了我們一個啟示。

我們需要的是一個狀態機的映象資料,這個資料在我們需要去拷貝的時候是可以隨時停止寫入的,那麼只要我們有了這個映象資料,我們就可以刪除paxos log了。

Checkpoint

這個狀態機的映象資料我們就稱之為Checkpoint。如何去生成Checkpoint,一個狀態機能在不停寫的情況下生成一個映象資料麼?答案是不確定的,看你要實現的狀態機是什麼,有的或許可以並很容易,有的可以但很難,有得可能根本無法實現。那這個問題又拋回給paxos庫了,我要想辦法去給他生成一個映象資料,並且由我控制。

一個狀態機能構建出一份狀態資料,那麼搞一個映象狀態機就可以同樣構建出一份映象狀態資料了。

這裡寫圖片描述

如上圖,用兩個狀態轉移完全一致的狀態機,分別管理不同的狀態資料,通過灌入相同的paxos log,最終出來的狀態資料是完全一致的。

在真正生產級的paxos庫裡面,這個特性太為重要了。我們實際實現通過一個非同步執行緒來構建這個映象資料,而當發現其他機器需要獲取這份資料的時候,可以很輕易的停止執行緒的工作,使得這份資料不再寫入。最後傳送給別的機器使用。

在目前的實現版本,我們真正做到了刪paxos log,新機啟動獲取checkpoint,資料對齊的完全自動化。也就是說,首先程式會根據磁碟使用情況自動刪除paxos log,其次,程式自動的通過映象狀態機生成checkpoint,最後,當一個新機器啟動的時候,可以自動的獲取到checkpoint,然後通過Learner自動的對齊剩下的資料,從而自動的完成無人工介入的機器更換。

正確性保證

分散式演算法是很難在工程上去驗證他的正確性的,我們只能在工程上利用各種手段去接近正確,這裡包括了執行前的測試,執行中的對賬,拜占庭問題的細化解決。

模擬非同步通訊環境

我們對演算法核心的構建過程中,使用了記憶體佇列來模擬網路通訊,使用一個程序來模擬一個機器。程序通過記憶體佇列來通訊。我們對記憶體佇列加以修改,使其支援出隊的延遲,丟失,以及亂序,使得整個通訊過程能按我們配置的方式來執行。我們通過配置不同的丟失率,延遲時間,以及亂序程度,驗證不同引數構造的環境下,paxos的工作效果以及一致性是否得到保證。而我們通過鉤子將程序頻繁殺掉重啟,以及寫盤方面的控制,模擬機器當機重啟。

執行時的對賬

採用crc32演算法,對有序的多個值進行累加校驗,得到一個當前資料版本的校驗值,通過不斷的在執行過程中比對每個當前編號例項對應的累加資料校驗值,一旦發現機器間校驗值不相同,則進行core的處理,防止錯誤繼續擴散。

防止拜占庭問題帶來的錯誤

對於所有磁碟寫入的資料,都需要進行二次校驗,防止磁碟資料被串改。在發現數據被串改後,能及時的回滾到上一個校驗成功的資料,併產生報警。

最後

由於篇幅問題,這裡還有更多的有意思的優化,以及更為細節的有趣的問題,就先不做探討了。相信大家也發現了,這篇文章通篇都在說確定一個值,確定一個值,但就沒說到底是怎麼去確定一個值的。如果你覺得這篇文章有趣對你有啟發,那就去找下論文研究一下paxos到底是怎麼確定一個值的吧。