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