在分布式計(jì)算領(lǐng)域,共識問題是最重要而基礎(chǔ)的問題。從表面上看含義很直接:可以粗略的理解為多個節(jié)點(diǎn)就某件事達(dá)成共識。乍看起來,你會覺得,這有什么難的?但不幸的是,很多系統(tǒng)都因?yàn)榈凸懒斯沧R算法的實(shí)現(xiàn)難度而問題百出。
盡管共識問題非常之重要,但在本書中直到現(xiàn)在才才被提及,似乎有點(diǎn)晚了。這是因?yàn)檫@個主題實(shí)在是太艱深了,而欣賞其精妙需要非常多的前置知識。即使在學(xué)術(shù)界,對共識問題的研究也是歷經(jīng)數(shù)十年坎坷才逐漸有了一些沉淀。在本書里,我們在第五章鋪墊了冗余(replication),在第七章鋪陳了事務(wù),在第八章探討了分布式系統(tǒng)的系統(tǒng)模型,在本章又討論了線性一致性和全序廣播,到現(xiàn)在,我們終于做足了準(zhǔn)備來好好談?wù)劰沧R問題了。
在很多場景下讓多個節(jié)點(diǎn)達(dá)成共識是非常重要的。比如:
共識的不可能性。你也許聽過 FLP —— 以 Fischer,Lynch 和 Paterson 三位作者姓名首字母命名的一種不可能原理——在網(wǎng)絡(luò)可靠,但允許節(jié)點(diǎn)宕機(jī)(即便只有一個)的異步模型系統(tǒng)中,不存在總是能夠達(dá)成共識的算法。在分布式系統(tǒng)中,我們又必須得假設(shè)節(jié)點(diǎn)可能會宕機(jī),因此穩(wěn)定可靠的共識算法是不存在的。但是,我們現(xiàn)在卻在探討可以達(dá)成共識的算法。這又是為啥?這可能嗎?
答案是,F(xiàn)LP 不可能是基于異步系統(tǒng)模型(參見系統(tǒng)模型和現(xiàn)實(shí))證明的,這是一種非常苛刻的模型,不能夠使用任何時鐘系統(tǒng)和超時檢測。如果允許使用超時宕機(jī)檢測、或者任何可以識別節(jié)點(diǎn)宕機(jī)的方法,就能夠?qū)崿F(xiàn)可靠的共識算法。甚而,只讓算法用隨機(jī)數(shù)來進(jìn)行故障檢測,也能夠繞過這個不可能定理。
因此,盡管在理論上,F(xiàn)LP 定理非常重要,斷言異步網(wǎng)絡(luò)中共識不可能達(dá)到;但在實(shí)踐中,分布式系統(tǒng)達(dá)成共識是可行的。
在本小節(jié),我們首先會詳細(xì)探討原子提交。特別的,我們將會討論兩階段提交(2PC,two-phase commit)算法,這是一種解決原子提交的最為常見的算法,很多數(shù)據(jù)庫和服務(wù)端應(yīng)用都實(shí)現(xiàn)了該算法。可以看出,2PC 在某種程度上是一種共識協(xié)議——雖然不是很完美。
在學(xué)習(xí)完 2PC 之后,我們將會轉(zhuǎn)向更完善的共識算法,比如 Zookeeper 中用的 Zab 算法和 etcd 中用的 Raft 算法。
在第七章我們探討過,在多個寫操作中途出現(xiàn)故障時,原子性能夠?qū)?yīng)用層提供一種簡單的語義。事務(wù)結(jié)果是要么成功提交(事務(wù)的全部寫入都成功持久化),要么全部丟棄(事務(wù)的所有寫入都被回滾,即取消或者扔掉)。
原子性能夠避免失敗的事務(wù)通過半完成(half-finished)或者半更新(half-updated)的結(jié)果來破壞數(shù)據(jù)庫系統(tǒng)。這一點(diǎn)對于多對象事務(wù)(參見單對象和多對象操作)和支持二級索引的數(shù)據(jù)庫來說尤為重要。二級索引是獨(dú)立于原始數(shù)據(jù)的一種數(shù)據(jù)結(jié)構(gòu),因此如果你更新了原始數(shù)據(jù),對應(yīng)的二級索引也需要進(jìn)行同步更新。原子性能夠保證二級索引和原始數(shù)據(jù)時刻保持一致。(如果索引不和原始數(shù)據(jù)保持同步更新,那該索引就失去了其作用)
對于運(yùn)行在單機(jī)數(shù)據(jù)上的事務(wù),原子提交通常由存儲引擎層來實(shí)現(xiàn)。當(dāng)客戶端請求數(shù)據(jù)庫節(jié)點(diǎn)提交事務(wù)時,數(shù)據(jù)庫會首先將事務(wù)所涉及到的寫入進(jìn)行持久化(通常通過寫前日志 WAL 的方式,參見讓 B 樹更可靠),事務(wù)結(jié)束時在硬盤上追加一個特殊的提交記錄(commit record)到日志上。如果數(shù)據(jù)庫在處理事務(wù)的過程中宕機(jī)了,在重啟時會從日志上對事務(wù)進(jìn)行恢復(fù):
因此,在單機(jī)數(shù)據(jù)庫里,事務(wù)是否提交主要取決于數(shù)據(jù)持久化到磁盤的順序:首先是數(shù)據(jù),接著是提交記錄。提交事務(wù)還是中止事務(wù),決定性時刻在于提交記錄成功刷盤的那一瞬間:在此之前,事務(wù)可能會被中止(由于宕機(jī));在此之后,該事務(wù)一定會被提交(即使宕機(jī))。也即,是唯一的硬件設(shè)備(某個特定節(jié)點(diǎn)上的某個具體的磁盤驅(qū)動)保證了提交的原子性。
然而,當(dāng)事務(wù)涉及到多個節(jié)點(diǎn)時又當(dāng)如何?例如,一個跨分區(qū)的多對象事務(wù),或者基于關(guān)鍵詞分區(qū)的二級索引(在該情況下,索引數(shù)據(jù)和基礎(chǔ)數(shù)據(jù)可能不在一個分區(qū)里,參見分片和次級索引)。大多數(shù)“NoSQL”分布式存儲不支持這種跨節(jié)點(diǎn)的分布式事務(wù),但很多分布式關(guān)系型數(shù)據(jù)庫則支持。
在上述場景中,只是簡單地在提交事務(wù)時給每個節(jié)點(diǎn)發(fā)送提交請求讓其提交事務(wù),是不能夠滿足事務(wù)基本要求的。這是因?yàn)椋赡苡械墓?jié)點(diǎn)成功提交了,有的節(jié)點(diǎn)卻提交失敗了,從而違反了原子性保證:
如果有些節(jié)點(diǎn)提交了該事務(wù),但另外的一些節(jié)點(diǎn)卻中止該事務(wù)了,多個節(jié)點(diǎn)間就會處于不一致的狀態(tài)。而且,一旦事務(wù)在一個節(jié)點(diǎn)上提交了(即便之后發(fā)現(xiàn)了該事務(wù)在其他節(jié)點(diǎn)上失敗了)就難以進(jìn)行撤銷。由于這個原因,我們需要僅在確信所有相關(guān)節(jié)點(diǎn)都能成功提交時,本節(jié)點(diǎn)才能提交。
事務(wù)提交后是不可撤銷的——在事務(wù)提交后,你不能再改變主意說,我要重新中止這個事務(wù)。這是因?yàn)椋坏┦聞?wù)提交了,就會對其他事務(wù)可見,從而可能讓其他事務(wù)依賴于該事務(wù)的結(jié)果做出一些新的決策;這個原則構(gòu)成了讀已提交(read commited)隔離級別的基礎(chǔ)(參見讀已提交)。如果事務(wù)允許在提交后中止,其他已經(jīng)讀取了該事務(wù)結(jié)果的事務(wù)也會失效,從而引起事務(wù)的級聯(lián)中止。
當(dāng)然,事務(wù)所造成的結(jié)果在事實(shí)上是可以被撤銷的,比如,通過補(bǔ)償事務(wù)(_compensating transaction_)。但,從數(shù)據(jù)庫的視角來看,這就是另外一個事務(wù)了;而跨事務(wù)的正確性,需要應(yīng)用層自己來保證。
兩階段提交(2PC,two-phase commit)是一種在多個節(jié)點(diǎn)上實(shí)現(xiàn)原子事務(wù)的算法——即,保證所有節(jié)點(diǎn)要么都提交,要么都中止。這是數(shù)據(jù)庫中一個經(jīng)典的算法。2PC 算法會在某些數(shù)據(jù)庫內(nèi)部使用,有時也會以 XA 事務(wù)(支持 Java 事務(wù) API)或者 SOAP Web 服務(wù)原子事務(wù)形式,供應(yīng)用層使用。
2PC 基本流程如下圖所示。相比單機(jī)事務(wù)的一次提交請求,2PC 中的提交、中止過程被拆分成了兩個階段(即名字由來)。
一次成功執(zhí)行的兩階段提交
不要混淆 2PC 和 2PL。Two-phase commit (2PC) 和 two-phase locking (2PL,參見兩階段鎖) 是兩個完全不同的概念。2PC 是為了在分布式系統(tǒng)中進(jìn)行原子提交,而 2PL 是為了進(jìn)行事務(wù)并發(fā)控制的一種加鎖方式。為了避免歧義,可以忽略他們在名字簡寫上的相似性,而把它們當(dāng)成完全不同的概念。
2PC 引入了一個單機(jī)事務(wù)中沒有的角色:協(xié)調(diào)者(coordinator,有時也被稱為事務(wù)管理器,transaction manager)。協(xié)調(diào)者通常以庫的形式出現(xiàn),并會嵌入到請求事務(wù)的應(yīng)用進(jìn)程中,但當(dāng)然,它也可以以單獨(dú)進(jìn)程或者服務(wù)的形式出現(xiàn)。比如說,Narayana, JOTM, BTM, or MSDTC.
和單機(jī)事務(wù)一樣,2PC 事務(wù)通常也由應(yīng)用層對多個節(jié)點(diǎn)上的數(shù)據(jù)讀寫開始。和協(xié)調(diào)者相對,我們將這些數(shù)據(jù)節(jié)點(diǎn)稱為事務(wù)的參與者(participants)。當(dāng)應(yīng)用層準(zhǔn)備好提交后,協(xié)調(diào)者開始階段一:向每個參與者發(fā)送 prepare 請求,詢問他們是否能夠提交。然后,協(xié)調(diào)者會根據(jù)參與者的返回而進(jìn)行下一步動作:
這個過程在某種程度上很像西方文化中的結(jié)婚儀式:牧師會分別問新娘、新郎是否愿意與對方結(jié)婚,通常,雙方都會回答“我愿意”(I do)。當(dāng)牧師收到雙方肯定的回答后,就會宣布他們結(jié)為夫婦:即事務(wù)提交,并將這個令人高興的事實(shí)傳達(dá)給所有賓客。如果新娘、新郎有任何一方回答否,則儀式中止。
從上面的簡要描述中,我們可能很難想通為什么兩階段提交能夠保證原子性?而多個節(jié)點(diǎn)的單階段提交就做不到這一點(diǎn)。畢竟,雖然是兩階段,但是兩階段中的任何一個請求都有可能在網(wǎng)絡(luò)中丟失。讓 2PC 能夠保證原子性的核心原因到底是什么?
為了理解它的工作原理,我們把 2PC 各個階段拆得更細(xì)一些:
因此,該協(xié)議有兩個重要的“不可回退點(diǎn)”:
這兩個承諾保證了 2PC 的原子性(其實(shí)單機(jī)事務(wù)是將上述兩個事件合二為一:將提交記錄寫入事務(wù)日志即代表提交)。
說回婚禮的比喻,在說“我愿意”之前,雙方都有說“沒門”(或者任何相當(dāng)言論)來中止事務(wù)的自由。然而,一旦承諾“我愿意”,就不能收回該承諾。即使你在說出“我愿意”之后昏倒過去,哪怕沒有聽到牧師說“你們現(xiàn)在已結(jié)為夫妻”,也不影響對應(yīng)事務(wù)已經(jīng)提交的事實(shí)。當(dāng)你之后恢復(fù)意識時,可以憑借事務(wù) ID 向牧師詢問你們的婚姻狀態(tài),或者簡單的等待牧師下一次重試的提交請求(重試會在你昏迷期間一直進(jìn)行)。
我們已經(jīng)討論了在 2PC 中如果任何一個參與者(participant)或者網(wǎng)絡(luò)故障時的系統(tǒng)行為:
然而,我們還沒有討論,當(dāng)協(xié)調(diào)者故障(coordinator failure)時,系統(tǒng)應(yīng)當(dāng)如何應(yīng)對。
如果協(xié)調(diào)者在準(zhǔn)備提交請求發(fā)送前故障,則參與者可以放心的中止事務(wù)。然而,一旦參與者收到準(zhǔn)備提交請求,并且回復(fù)“可以”,則根據(jù) 2PC 設(shè)定,它不能單方面的中止事務(wù)——而必須等待協(xié)調(diào)者的提交或者中止請求。如果此時協(xié)調(diào)者宕機(jī)或者網(wǎng)絡(luò)故障,則參與者只能死等。參與者事務(wù)的這種狀態(tài)稱為存疑(in doubt)或者未定(uncertain)。
圖 9-10 就是一個這樣的例子。在該例子中,系統(tǒng)處于第二階段,協(xié)調(diào)者準(zhǔn)備提交,并且數(shù)據(jù)庫實(shí)例 2 收到了提交請求。此時,協(xié)調(diào)者宕機(jī),還沒來得及給數(shù)據(jù)庫實(shí)例 1 發(fā)送提交請求,因此該實(shí)例不知道是要提交還是中止事務(wù)。超時機(jī)制在這里并不能解決問題:超時后,如果數(shù)據(jù)庫實(shí)例 1 單方面決定中止事務(wù),則會和數(shù)據(jù)庫實(shí)例 2 處于不一致的狀態(tài)。類似的,單方面提交事務(wù)也不靠譜,畢竟另外的參與者也可能收到請求并中止了事務(wù)。
第一階段后協(xié)調(diào)者故障
在未收到協(xié)調(diào)者的消息前,參與者無從得知是要提交還是中止。原則上,參與者之間可以互相溝通以確定該如何進(jìn)行下一步,并最終達(dá)到一致,但這已經(jīng)超脫了 2PC 協(xié)議范疇。
在 2PC 中,唯一使算法能夠完成的方法就是等待協(xié)調(diào)者恢復(fù)。這也是為什么,協(xié)調(diào)者在給參與者發(fā)送提交或者中止消息時,需要先將該決策寫入事務(wù)日志中:當(dāng)協(xié)調(diào)者恢復(fù)時,他就能從事務(wù)日志中讀取該決策,以讓所有處于未決狀態(tài)的參與者狀態(tài)確定下來。如果協(xié)調(diào)者恢復(fù)了,發(fā)現(xiàn)并沒有寫入任何決策到事務(wù)日志中,則中止該事務(wù)。因此,2PC 的提交點(diǎn)(commit point)最終可以歸結(jié)到協(xié)調(diào)者上的單機(jī)原子提交。
本文鏈接:http://www.tebozhan.com/showinfo-26-35327-0.htmlDDIA:一文帶你了解“兩階段提交”
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 嵌入式軟件設(shè)計(jì)原則隨想