【转载】架构师需要了解的Paxos原理、历程及实战
原文鏈接,請參見:http://weibo.com/ttarticle/p/show?id=2309403952892003376258
數據庫高可用性難題
數據庫的數據一致和持續可用對電子商務和互聯網金融的意義不言而喻,而這些業務在使用數據庫時,無論 MySQL 還是 Oracle,都會面臨一個艱難的取舍,就是如何處理主備庫之間的數據同步。對于傳統的主備模式或者一主多備模式,我們都需要考慮的問題,就是與備機保持強同步還是異步復制。
對于強同步模式,要求主機必須把 Redolog 同步到備機之后,才能應答客戶端,一旦主備之間出現網絡抖動,或者備機宕機,則主機無法繼續提供服務,這種模式實現了數據的強一致,但是犧牲了服務的可用性,且由于跨機房同步延遲過大使得跨機房的主備模式也變得不實用。而對于異步復制模式,主機寫本地成功后,就可以立即應答客戶端,無需等待備機應答,這樣一旦主機宕機無法啟動,少量不同步的日志將丟失,這種模式實現了服務持續可用,但是犧牲了數據一致性。這兩種方式對應的就是 Oracle 的 Max Protection 和 Max Performance 模式,而 Oracle 另一個最常用的 Max Availability 模式,則是一個折中,在備機無應答時退化為 Max Performance 模式,我認為本質上還是異步復制。主備模式還有一個無法繞過的問題,就是選主,最簡單山寨的辦法,搞一個單點,定時 Select 一下主機和各個備機,貌似 MHA 就是這個原理,具體實現細節我就不太清楚了。一個改進的方案是使用類似 ZooKeeper 的多點服務替代單點,各個數據庫機器上使用一個 Agent 與單點保持 Lease,主機 Lease 過期后,立即置為只讀。改進的方案基本可以保證不會出現雙主,而缺點是 ZooKeeper 的可維護性問題,以及多級 Lease 的恢復時長問題(這個本次就不展開講了,感興趣的同學請參考這篇文章? http://oceanbase.org.cn/
Paxos 協議簡單回顧
主備方式處理數據庫高可用問題有上述諸多缺陷,要改進這種數據同步方式,我們先來梳理下數據庫高可用的幾個基本需求:
使用Paxos協議的日志同步可以實現這三個需求,而 Paxos 協議需要依賴一個基本假設,主備之間有多數派機器(N / 2 + 1)存活并且他們之間的網絡通信正常,如果不滿足這個條件,則無法啟動服務,數據也無法寫入和讀取。我們先來簡單回顧一下 Paxos 協議的內容,首先,Paxos 協議是一個解決分布式系統中,多個節點之間就某個值(提案)達成一致(決議)的通信協議。它能夠處理在少數派離線的情況下,剩余的多數派節點仍然能夠達成一致。然后,再來看一下協議內容,它是一個兩階段的通信協議,推導過程我就不寫了(中文資料請參考這篇 Http://t.cn/R40lGrp ),直接看最終協議內容:
1、第一階段 Prepare
P1a:Proposer 發送 Prepare
Proposer 生成全局唯一且遞增的提案 ID(Proposalid,以高位時間戳 + 低位機器 IP 可以保證唯一性和遞增性),向 Paxos 集群的所有機器發送 PrepareRequest,這里無需攜帶提案內容,只攜帶 Proposalid 即可。
P1b:Acceptor 應答 PrepareAcceptor 收到 PrepareRequest 后,做出“兩個承諾,一個應答”。
兩個承諾:
- 第一,不再應答 Proposalid 小于等于(注意:這里是 <= )當前請求的 PrepareRequest;
- 第二,不再應答 Proposalid 小于(注意:這里是 < )當前請求的 AcceptRequest
一個應答:
- 返回自己已經 Accept 過的提案中 ProposalID 最大的那個提案的內容,如果沒有則返回空值;
注意:這“兩個承諾”中,蘊含兩個要點:
2、第二階段 Accept
P2a:Proposer 發送 Accept“提案生成規則”:Proposer 收集到多數派應答的 PrepareResponse 后,從中選擇proposalid最大的提案內容,作為要發起 Accept 的提案,如果這個提案為空值,則可以自己隨意決定提案內容。然后攜帶上當前 Proposalid,向 Paxos 集群的所有機器發送 AccpetRequest。
P2b:Acceptor 應答 Accept
Accpetor 收到 AccpetRequest 后,檢查不違背自己之前作出的“兩個承諾”情況下,持久化當前 Proposalid 和提案內容。最后 Proposer 收集到多數派應答的 AcceptResponse 后,形成決議。
這里的“兩個承諾”很重要,后面也會提及,請大家細細品味。
Basic Paxos 同步日志的理論模型
上面是 Lamport 提出的算法理論,那么 Paxos 協議如何具體應用在 Redolog 同步上呢,我們先來看最簡單的理論模型,就是在 N 個 Server的機群上,持久化數據庫或者文件系統的操作日志,并且為每條日志分配連續遞增的 LogID,允許多個客戶端并發的向機群內的任意機器發送日志同步請求。在這個場景下,不同 Logid 標識的日志都是一個個相互獨立的 Paxos Instance,每條日志獨立執行完整的 Paxos 兩階段協議。
因此在執行 Paxos 之前,需要先確定當前日志的 Logid,理論上對每條日志都可以從 1 開始嘗試,直到成功持久化當前日志,但是為了降低失敗概率,可以先向集群內的 Acceptor 查詢他們 PrepareResponse 過的最大 Logid,從多數派的應答結果中選擇最大的 Logi-d,加 1 后,作為本條日志的 Logid。然后以當前 Logid 標識 Paxos Instance,開始執行Paxos兩階段協議。可能出現的情況是,并發情況下,當前 Logid 被其他日志使用,那么在 P2a 階段確定的提案內容可能就不是自己本次要同步的日志內容,這種情況下,就要重新決定logid,然后重新開始執行 Paxos 協議。考慮幾種異常情況,Proposer 在 P1b 或 P2b 階段沒有收到多數派應答,可能是受到了其他 Logid 相同而 Proposalid 更大的 Proposer 干擾,或者是網絡、機器等問題,這種情況下則要使用相同的 Logid,和新生成的 Proposalid 來重新執行 Paxos 協議。恢復時,按照 Logid 遞增的順序,針對每條日志執行完整 Paxos 協議成功后,形成決議的日志才可以進行回放。那么問題來了:比如 A/B/C 三個 Server,一條日志在 A/B 上持久化成功,已經形成多數派,然后B宕機;另一種情況,A/B/C 三個 Server,一條日志只在A 上持久化成功,超時未形成多數派,然后B宕機。上述兩種情況,最終的狀態都是 A 上有這條日志,C 上沒有,那么應該怎么處理呢?這里提一個名詞:“最大 Commit 原則”,這個陽振坤博士給我講授 Paxos 時提出的名詞,我覺得它是 Paxos 協議的最重要隱含規則之一,一條超時未形成多數派應答的提案,我們即不能認為它已形成決議,也不能認為它未形成決議,跟“薛定諤的貓”差不多,這條日志是“又死又活”的,只有當你觀察它(執行 Paxos 協議)的時候,你才能得到確定的結果。因此對于上面的問題,答案就是無論如何都對這條日志重新執行 Paxos。這也是為什么在恢復的時候,我們要對每條日志都執行 Paxos 的原因。
Multi Paxos 的實際應用
上述 Basic-Paxos 只是理論模型,在實際工程場景下,比如數據庫同步 Redolog,還是需要集群內有一個 leader,作為數據庫主機,和多個備機聯合組成一個 Paoxs 集群,對?Redolog 進行持久化。此外持久化和回放時每條日志都執行完整 Paxos 協議(3 次網絡交互,2 次本地持久化),代價過大,需要優化處理。因此使用 Multi-Paxos 協議,要實現如下幾個重要功能:
我在剛剛學習 Paxos 的時候,曾經認為選主就是跑一輪 Paxos 來形成“誰是 leader”的決議,其實并沒有這么簡單,因為 Paxos 協議的基本保證就是一旦形成決議,就不能更改,那么再次選新主就沒辦法處理了。因此對“選主”,需要變通一下思路,還是執行 Paxos 協議,但是我們并不關心決議內容,而是關心“誰成功得到了多數派的 AcceptResponse”,這個 Server 就是選主產生的 Leader。而多輪選主,就是針對同一個 Paxos Instance 反復執行,最后贏得多數派 Accept 的 Server 就是“當選 Leader”。
不幸的是執行 Paxos 勝出的“當選 Leader”還不能算是真正的 Leader,只能算是“當選 Leader”,就像美國總統一樣,“當選總統”是贏得選舉的總統,但是任期還未開始他還不是真正的總統。在 Multi-Paxos 中因為可能存在多個 Server 先后贏得了選主,因此新的“當選leader”還要立即寫出一條日志,以確認自己的 Leader 身份。這里就順勢引出日志同步邏輯的簡化,我們將 Leader 選主看作 Paxos 的 Prepare 階段,這個 Prepare 操作在邏輯上一次性的將后續所有即將產生的日志都執行 Prepare,因此在 Leader任期內的日志同步,都使用同一個 Proposalid,只執行 Accept 階段即可。那么問題來了,各個備機在執行 Accept 的時候,需要注意什么?
答案是上面提到過的“兩個承諾”,因為我們已經把選主的那輪 Paxos 看做 Prepare 操作了,所以對于后續要 Accept 的日志,要遵守“兩個承諾”。所以,對于先后勝出選主的多個“當選 Leader”,他們同步日志時攜帶的 Proposalid 的大小是不同的,只有最大的 Pro-posalid 能夠同步日志成功,成為正式的 Leader。再進一步簡化,選主 Leader 后,“當選 Leader”既然必先寫一條日志來確認自己的 Leader身份,而協議允許多個“當選 Leader”產生,那么選主過程的本質其實就是為了拿到各個備機的“兩個承諾”而已,選主過程本身產生的決議內容并沒有實際意義,所以可以進一步簡化為只執行 Prepare 階段,而無需執行 Accept。再進一步優化,與 Raft 協議不同,Multi-Paxos 并不要求新任 Leader 本地擁有全部日志,因此新任 Leader 本地可能與其他 Server 相差了一些日志,它需要知道自己要補全哪些日志,因此它要向多數派查詢各個機器上的 MaxLogD,以確定補全日志的結束 LogID。這個操作成為 GetMaxLogID,我們可以將這個操作與選主的 Prepare 操作搭車一起發出。這個優化并非 Multi-Paxos 的一部分,只是一個工程上比較有效的實現。回放邏輯的簡化就比較好理解了,Leader 對每條形成多數派的日志,異步的寫出一條“確認日志”即可,回放時如果一條日志擁有對應的“確認日志”,則不需要重新執行 Paoxs,直接回放即可。對于沒有“確認日志”的,則需要重新執行 Paxos。工程上為了避免“確認日志”與對應的 Redolog 距離過大而帶來回放的復雜度,往往使用滑動窗口機制來控制他們的距離。同時“確認日志”也用來提示備機可以回放收到的日志了。與 Raft 協議不同,由于 Multi-Paxos 允許日志不連續的確認(請思考:不連續確認的優勢是什么?),以及允許任何成員都可以當選 Leader,因此新任 leader 需要補全自己本地缺失的日志,以及對未“確認”的日志重新執行 Paxos。我把這個過程叫做日志的“重確認”,本質上就是按照“最大commit原則”,使用當前最新的 Proposalid,逐條的對這些日志重新執行 Paxos,成功后再補上對應的“確認日志”。
相對于 Raft 連續確認的特性,使用 Multi-Paxos 同步日志,由于多條日志間允許亂序確認,理論上會出現一種被稱我們團隊同學戲稱為“幽靈復現”的詭異現象,如下圖所示(圖片引用自我的博客)
第一輪中A被選為 Leader,寫下了 1-10 號日志,其中 1-5 號日志形成了多數派,并且已給客戶端應答,而對于 6-10 號日志,客戶端超時未能得到應答。
第二輪,A 宕機,B 被選為 Leader,由于 B 和 C 的最大的 LogID 都是 5,因此 B 不會去重確認 6 - 10 號日志,而是從 6 開始寫新的日志,此時如果客戶端來查詢的話,是查詢不到上一輪 6 - 10 號 日志內容的,此后第二輪又寫入了 6 - 20 號日志,但是只有 6 號和 20 號日志在多數派。
第三輪,A 又被選為 Leader,從多數派中可以得到最大 LogID 為 20,因此要將 7 - 20 號日志執行重確認,其中就包括了 A 上的 7-10 號日志,之后客戶端再來查詢的話,會發現上次查詢不到的 7 - 10 號日志又像幽靈一樣重新出現了。處理“幽靈復現”問題,需要依賴新任 Leader 在完成日志重確認,開始寫入新的 Redolog 之前,寫出一條被稱為 StartWorking 的日志,這條日志的內容中記錄了當前 Leader 的? EpochID( 可以使用 Proposalid 的值),并且 Leader 每寫一條日志都在日志內容中攜帶現任 Leader 的 EpochID。回放時,經過了一條 StartWorking 日志之后,再遇到 EpochID 比它小的日志,就直接忽略掉,比如按照上面例子畫出的這張圖,7 - 19 號日志要在回放時被忽略掉。
?依賴時鐘誤差的變種 Paxos 選主協議簡單分析
阿里的陽振坤老師根據 Paxos 協議設計了一個簡化版本的選主協議,相對 MultiPaxos 和 Raft 協議的優勢在于,它不需要持久化任何數據,引入選主窗口的概念,使得大部分場景下集群內的所有機器能夠幾乎同時發起選主請求,便于投票時比對預定的優先級。下面的圖引用自 OB 團隊在公開場合分享 PPT 中的圖片。
如圖所示,選主協議規定選主窗口開啟是當前時間對一個T取余為0的時間,即只能在第 0,T,2T,3T...N*T 的時間點上開啟選主窗口,協議將一次選主劃分為三個階段
假設時鐘誤差最大為 Tdiff,網絡網路傳輸單程最長耗時為 Tst
因此最差情況下,選主開始后,經過 Tdiff × 6 + Tst × 3 的 d 時間,就可以選出 Leader 各個成員投出選票后,就從自己的 T1 時刻開始計時,認為 leader 持續 lease 時間內有效,在 Lease 有效期內,Leader 每隔 Telect 的時間就向其他成員發出續約請求,將 Lease 時間順延一個 Telect,如果 Lease 過期后 Leader 沒有續約,則各個成員等待下一個選主窗口到來后發起選主。因此最差情況下的無主時間是:Lease 時間 + Telect + 選主窗口間隔時間 T。這個選主算法相對 Paxos 和 Raft 更加簡單,但是對時鐘誤差有比較強的依賴,時鐘誤差過大的情況下,會造成投票分裂無法選出主,甚至可能出現雙主(不過話說任何保持 Leader 身份的 Lease 機制都得依賴時鐘…),因此可能僅僅適合 BAT 這種配備了原子鐘和 GPS 校準時鐘,能夠控制時鐘誤差在 100ms 以內的土豪機房。2015 年閏秒時,這個選主算法已經上線至支付寶,當時測試了幾個月吧,1 秒的跳變已經太大,當時測試了幾個月,修改 ntp 配置緩慢校準,最后平穩渡過。
Q & A
1、ZooKeeper 所使用的 zad 協議與 Paxos 協議有什么區別?
2、Paxos 能完成在全球同步的業務嗎?理論上支持多少機器同步?Paxos 成員組橫跨全球的案例我還沒有見過 Paper,我個人認為它并不適合全球不同,原因是延遲太大,但是 Google 的 Spanner 和 Amazon 的 Aurora 都實現了橫跨北美多 IDC 的同步;理論上多少都行,你能接受延遲就可以。
3、問個問題,能否簡單說說 Raft 算法和 Paxos 算法的異同?應用場的異同?
Raft 可以認為是一種簡化的 Multi-Paxos 實現,他的最大簡化之處在于備機接受 Leader 日志的前提是收到 LogID 連續的日志,在這個假設前提下,沒有我文中提到的“幽靈復現”和“重確認”問題。簡化帶來的代價是對網絡抖動的容忍度稍低一些,考慮這樣的場景 ABC 三臺機器,C 臨時下線一會錯過一些日志,然后 C上 線了,但是在 C 補全日志之前,AB 如果再宕機一臺的話,服務就停了。
4、Paxos 實現是獨立的庫或服務還是和具體的業務邏輯綁定,上線前如何驗證 Paxos 算法實現的正確性?
OB 實現的 Paxos 是和事務 Redolog 庫比較緊耦合的,沒有獨立的庫;測試方案一個是 Monkey tests,隨機模擬各種異常環境,包括斷網、網絡延遲、機器宕機、包重復到達等情況保持壓力和異常;另外一個是做了一個簡易的虛擬機,來解釋測試 Case,通過人工構造多種極端的場景,來是系統立即進入一個“夢境”。
5、Logid 和 proposalid都應該是不能重復的,這個是如何保證的?原子鐘的精確性僅僅是為了選主嗎?
首先,Leader 任期內,Logid 只由 Leader 產生,沒有重復性的問題;
第二,Leader 產生后,會執行 GetMaxLogID,從集群多數派拿到最大的 Logid,加以后作為本屆任期內的 Logid 起點,這也可以保證有效日志 logid 不重復。Proposalid,高位使用 64 位時間戳,低位使用 IP 地址,可以保證唯一性和遞增性。
6、在用 Paxos 協議做 Master 和 Slave 一致性保證時,Paxos 日志回放應該怎樣去做?
Master 形成多數派確認后,異步的寫出“確認日志”,Slave 回放到確認日志之后,才能去回放收到的正常日志。因此一般情況下,備機總是要落后主機一點點的。
總結
以上是生活随笔為你收集整理的【转载】架构师需要了解的Paxos原理、历程及实战的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Golang Clearing sli
- 下一篇: 什么是性能测试