作者:allanpan,騰訊 IEG 高級后臺工程師 導(dǎo)言分布式事務(wù)是分布式系統(tǒng)必不可少的組成部分,基本上只要實現(xiàn)一個分布式系統(tǒng)就逃不開對分布式事務(wù)的支持。本文從分布式事務(wù)這個概念切入,嘗試對分布式事務(wù)以及分布式系統(tǒng)最核心的底層原理逐一進(jìn)行剖析,內(nèi)容包括但不限于 BASE 原則、兩階段原子提交協(xié)議、三階段原子提交協(xié)議、Paxos/Multi-Paxos 分布式共識算法的原理與證明、Raft 分布式共識算法和分布式事務(wù)的并發(fā)控制等內(nèi)容。 事務(wù)事務(wù)是訪問并可能更新各種數(shù)據(jù)項的一個程序執(zhí)行單元(unit)。事務(wù)由一個或多個步驟組成,一般使用形如 begin transaction 和 end transaction 語句或者函數(shù)調(diào)用作為事務(wù)界限,事務(wù)內(nèi)的所有步驟必須作為一個單一的、不可分割的單元去執(zhí)行,因此事務(wù)的結(jié)果只有兩種:1. 全部步驟都執(zhí)行完成,2. 任一步驟執(zhí)行失敗則整個事務(wù)回滾。 事務(wù)最早由數(shù)據(jù)庫管理系統(tǒng)(database management system,DBMS)引入并實現(xiàn),數(shù)據(jù)庫事務(wù)是數(shù)據(jù)庫管理系統(tǒng)執(zhí)行過程中的一個邏輯單位,由一個有限的數(shù)據(jù)庫操作序列構(gòu)成。數(shù)據(jù)庫事務(wù)嚴(yán)格遵循 ACID 原則,屬于剛性事務(wù),一開始數(shù)據(jù)庫事務(wù)僅限于對單一數(shù)據(jù)庫資源對象的訪問控制,這一類事務(wù)稱之為本地事務(wù) (Local Transaction),后來隨著分布式系統(tǒng)的出現(xiàn),數(shù)據(jù)的存儲也不可避免地走向了分布式,分布式事務(wù)(Distributed Transaction)便應(yīng)運(yùn)而生。 剛性事務(wù)剛性事務(wù)(如單一數(shù)據(jù)庫事務(wù))完全遵循 ACID 規(guī)范,即數(shù)據(jù)庫事務(wù)的四大基本特性:
剛性事務(wù)也能夠以分布式 CAP 理論中的 CP 事務(wù)來作為定義。 柔性事務(wù)在電商領(lǐng)域等互聯(lián)網(wǎng)場景下,傳統(tǒng)的事務(wù)在數(shù)據(jù)庫性能和處理能力上都遇到了瓶頸。因此,柔性事務(wù)被提了出來,柔性事務(wù)基于分布式 CAP 理論以及延伸出來的 BASE 理論,相較于數(shù)據(jù)庫事務(wù)這一類完全遵循 ACID 的剛性事務(wù)來說,柔性事務(wù)保證的是 “基本可用,最終一致”,CAP 原理相信大家都很熟悉了,這里我們講一下 BASE 原則: 柔性事務(wù)(如分布式事務(wù))為了滿足可用性、性能與降級服務(wù)的需要,降低一致性(Consistency)與隔離性(Isolation)的要求,遵循 BASE 理論,傳統(tǒng)的 ACID 事務(wù)對隔離性的要求非常高,在事務(wù)執(zhí)行過程中,必須將所有的資源對象鎖定,因此對并發(fā)事務(wù)的執(zhí)行極度不友好,柔性事務(wù)(比如分布式事務(wù))的理念則是將鎖資源對象操作從本地資源對象層面上移至業(yè)務(wù)邏輯層面,再通過放寬對強(qiáng)一致性要求,以換取系統(tǒng)吞吐量的提升。 此外,雖然柔性事務(wù)遵循的是 BASE 理論,但是還需要遵循部分 ACID 規(guī)范: 本地事務(wù)本地事務(wù)(Local Transaction)指的是僅僅對單一節(jié)點(diǎn)/數(shù)據(jù)庫資源對象進(jìn)行訪問/更新的事務(wù),在這種事務(wù)模式下,BASE 理論派不上用場,事務(wù)完全遵循 ACID 規(guī)范,確保事務(wù)為剛性事務(wù)。 分布式事務(wù)在分布式架構(gòu)成為主流的當(dāng)下,系統(tǒng)對資源對象的訪問不能還局限于單節(jié)點(diǎn),多服務(wù)器、多節(jié)點(diǎn)的資源對象訪問成為剛需,因此,本地事務(wù)無法滿足分布式架構(gòu)的系統(tǒng)的要求,分布式事務(wù)應(yīng)運(yùn)而生。 訪問/更新由多個服務(wù)器管理的資源對象的平面事務(wù)或者嵌套事務(wù)稱之為分布式事務(wù)(Distributed Transaction),分布式事務(wù)是相對于本地事務(wù)來說的。 平面事務(wù):單一事務(wù),訪問多個服務(wù)器節(jié)點(diǎn)的資源對象,一個平面事務(wù)完成一次請求之后才能發(fā)起下一個請求。 嵌套事務(wù):多事務(wù)組成,頂層事務(wù)可以不斷創(chuàng)建子事務(wù),子事務(wù)又可以進(jìn)一步地以任意深度嵌套子事務(wù)。 對于分布式事務(wù)來說,有兩個最核心的問題: 問題 1 的解決需要引入一類分布式原子提交協(xié)議的算法如兩階段提交協(xié)議等,來對分布式事務(wù)過程中的提交或放棄決策進(jìn)行管理,并確保分布式提交的原子性。而問題 2 則由分布式事務(wù)的并發(fā)控制機(jī)制來處理。 原子提交協(xié)議
原子性約束要求在分布式事務(wù)結(jié)束之時,它的所有操作要么全部執(zhí)行,要么全部不執(zhí)行。以分布式事務(wù)的原子性來分析,客戶端請求訪問/更新多個服務(wù)器節(jié)點(diǎn)上的資源對象,在客戶端提交或放棄該事務(wù)從而結(jié)束事務(wù)之后,多個服務(wù)器節(jié)點(diǎn)的最終狀態(tài)要么是該事務(wù)里的所有步驟都執(zhí)行成功之后的狀態(tài),要么恢復(fù)到事務(wù)開始前的狀態(tài),不存在中間狀態(tài)。滿足這種約束的分布式事務(wù)協(xié)議則稱之為原子提交協(xié)議。 當(dāng)一個分布式事務(wù)結(jié)束時,事務(wù)的原子特性要求所有參與該事務(wù)的服務(wù)器節(jié)點(diǎn)必須全部提交或者全部放棄該事務(wù),為了實現(xiàn)這一點(diǎn),必須引入一個協(xié)調(diào)者(Coordinator)的角色,從參與事務(wù)的所有服務(wù)器節(jié)點(diǎn)中挑選一個作為協(xié)調(diào)者,由它來保證在所有服務(wù)器節(jié)點(diǎn)上最終獲得同樣的結(jié)果。協(xié)調(diào)者的工作原理取決于分布式事務(wù)選用的協(xié)議。 一般來說,分布式事務(wù)中包含的兩個最基礎(chǔ)的角色就是: 單階段原子提交協(xié)議單階段原子提交協(xié)議(one-phase atomic commit protocol, 1APC)是最簡單的一種原子提交協(xié)議,它通過設(shè)置一個協(xié)調(diào)者并讓它不斷地向所有參與者發(fā)送提交(commit)或放棄(abort)事務(wù)的請求,直到所有參與者確認(rèn)已執(zhí)行完相應(yīng)的操作。 1APC 協(xié)議的優(yōu)點(diǎn)是簡單易用,對一些事務(wù)不復(fù)雜的場景比較合適,但在復(fù)雜事務(wù)場景則顯得捉襟見肘,因為該協(xié)議不允許任何服務(wù)器節(jié)點(diǎn)單方面放棄事務(wù),事務(wù)的放棄必須由協(xié)調(diào)者來發(fā)起,這個設(shè)計會導(dǎo)致很多問題:首先因為只有一次通信,協(xié)調(diào)者并不會收集所有參與者的本地事務(wù)執(zhí)行的情況,所以協(xié)調(diào)者決定提交還是放棄事務(wù)只基于自己的判斷,在參與者執(zhí)行事務(wù)期間可能會遇到錯誤從而導(dǎo)致最終事務(wù)未能真正提交,錯誤一般與事務(wù)的并發(fā)控制有關(guān),比如事務(wù)執(zhí)行期間對資源對象加鎖,遇到死鎖,需要放棄事務(wù)從而解開死鎖,而協(xié)調(diào)者并不知道,因此在發(fā)起下一個請求之前,客戶端完全不知道事務(wù)已被放棄。另一種情況就是利用樂觀并發(fā)控制機(jī)制訪問資源對象,某一個服務(wù)器節(jié)點(diǎn)的驗證失敗將導(dǎo)致事務(wù)被放棄,而協(xié)調(diào)者完全不知情。 兩階段提交協(xié)議定義兩階段提交協(xié)議(two-phase commit protocol, 2PC)的設(shè)計初衷是為了解決 1APC 不允許任意一個服務(wù)器節(jié)點(diǎn)自行放棄它自己的那部分本地事務(wù)的痛點(diǎn),2PC 允許任何一個參與者自行決定要不要放棄它的本地事務(wù),而由于原子提交協(xié)議的約束,任意一個本地事務(wù)被放棄將導(dǎo)致整個分布式事務(wù)也必須放棄掉。 兩階段提交協(xié)議基于以下幾個假設(shè): 原理兩階段提交協(xié)議,顧名思義整個過程需要分為兩個階段: 在進(jìn)行兩階段提交的過程中,協(xié)調(diào)者會在以下四種狀態(tài)間流轉(zhuǎn): 而參與者則會在以下三種狀態(tài)間流轉(zhuǎn): 階段 I(投票表決階段) 階段 II(收集投票結(jié)果完成事務(wù)) 上面的過程是一種更通用的流程,即由任意的參與者發(fā)起一個分布式事務(wù),而在實踐中一般把分布式事務(wù)的發(fā)起交給協(xié)調(diào)者來做,減少事務(wù)發(fā)起者確認(rèn)該事務(wù)已被提交所需等待的網(wǎng)絡(luò)消息延遲: 性能網(wǎng)絡(luò) I/O 開銷假設(shè)兩階段提交過程一切運(yùn)行正常,即協(xié)調(diào)者和參與者都不出現(xiàn)崩潰和重啟,網(wǎng)絡(luò)通信也都正常。那么假設(shè)有一個協(xié)調(diào)者和 N 個參與者,兩階段提交過程中將會發(fā)送如下的消息: 所以,事務(wù)發(fā)起者在經(jīng)過 4 條網(wǎng)絡(luò)消息延遲之后確認(rèn)該分布式事務(wù)已被提交,而整個過程共計發(fā)送 3N - 1 條網(wǎng)絡(luò)消息(因為 haveCommitted 在 2PC 僅僅是用于最后通知協(xié)調(diào)者而已,屬于可有可無的一次網(wǎng)絡(luò)消息,2PC 在該消息缺省的情況下也能正常運(yùn)行,因此 haveCommitted 一般不計入網(wǎng)絡(luò)延遲成本中)。 前面我們提到,在實踐中一般是由協(xié)調(diào)者來發(fā)起事務(wù),如果考慮這種情況的話,事務(wù)發(fā)起者 -- 協(xié)調(diào)者在經(jīng)過 3 條網(wǎng)絡(luò)消息延遲之后確認(rèn)該分布式事務(wù)已經(jīng)被提交,而整個過程實際發(fā)送的網(wǎng)絡(luò)消息則變成 3N 條。 總而言之,兩階段提交協(xié)議的網(wǎng)絡(luò)通信開銷和集群節(jié)點(diǎn)的數(shù)量成 3 倍正比。 本地存儲設(shè)備 I/O 開銷基于前文中敘述的兩階段提交協(xié)議的基本假設(shè)之一:每個節(jié)點(diǎn)會通過日志來記錄在本地執(zhí)行的操作,以便在節(jié)點(diǎn)發(fā)生故障并重啟節(jié)點(diǎn)之后能利用日志恢復(fù)到故障前的狀態(tài),因此兩階段提交過程中除了網(wǎng)絡(luò) I/O 的開銷之外,還有本地存儲設(shè)備 I/O 的開銷: 所以事務(wù)發(fā)起者在經(jīng)過 3 次本地存儲設(shè)備 I/O 延遲之后確認(rèn)該事務(wù)已被提交,整個過程總計有 N + 1 次本地存儲設(shè)備 I/O,而如果由協(xié)調(diào)者來發(fā)起事務(wù)的話,則還是需要 N + 1 次本地存儲設(shè)備 I/O,但是只需要經(jīng)過 2 次本地存儲設(shè)備 I/O 延遲即可確認(rèn)事務(wù)已被提交。 恢復(fù)在分布式事務(wù)中,所有的參與者節(jié)點(diǎn)都可能發(fā)生故障,所以我們需要保證在該故障節(jié)點(diǎn)恢復(fù)時發(fā)生的一切都和分布式事務(wù) T 的全局決策保持一致。節(jié)點(diǎn)在恢復(fù)的時候會讀取 T 的最后一個本地日志記錄并作出相應(yīng)的操作: 缺陷XA 標(biāo)準(zhǔn)接口2PC 兩階段提交協(xié)議本身只是一個通用協(xié)議,不提供具體的工程實現(xiàn)的規(guī)范和標(biāo)準(zhǔn),在工程實踐中為了統(tǒng)一標(biāo)準(zhǔn),減少行業(yè)內(nèi)不必要的對接成本,需要制定標(biāo)準(zhǔn)化的處理模型及接口標(biāo)準(zhǔn),國際開放標(biāo)準(zhǔn)組織 Open Group 定義了分布式事務(wù)處理模型 DTP(Distributed Transaction Processing)Model,現(xiàn)在 XA 已經(jīng)成為 2PC 分布式事務(wù)提交的事實標(biāo)準(zhǔn),很多主流數(shù)據(jù)庫如 Oracle、MySQL 等都已經(jīng)實現(xiàn) XA。 兩階段事務(wù)提交采用的是 X/OPEN 組織所定義的 DTP Model 所抽象的 AP(應(yīng)用程序), TM(事務(wù)管理器)和 RM(資源管理器) 概念來保證分布式事務(wù)的強(qiáng)一致性。 其中 TM 與 RM 間采用 XA 的協(xié)議進(jìn)行雙向通信。 與傳統(tǒng)的本地事務(wù)相比,XA 事務(wù)增加了準(zhǔn)備階段,數(shù)據(jù)庫除了被動接受提交指令外,還可以反向通知調(diào)用方事務(wù)是否可以被提交。 TM 可以收集所有分支事務(wù)的準(zhǔn)備結(jié)果,并于最后進(jìn)行原子提交,以保證事務(wù)的強(qiáng)一致性。 Java 通過定義 JTA 接口實現(xiàn)了 XA 模型,JTA 接口中的 ResourceManager 需要數(shù)據(jù)庫廠商提供 XA 驅(qū)動實現(xiàn), TransactionManager 則需要事務(wù)管理器的廠商實現(xiàn),傳統(tǒng)的事務(wù)管理器需要同應(yīng)用服務(wù)器綁定,因此使用的成本很高。 而嵌入式的事務(wù)管器可以以 jar 包的形式提供服務(wù),同 Apache ShardingSphere 集成后,可保證分片后跨庫事務(wù)強(qiáng)一致性。 通常,只有使用了事務(wù)管理器廠商所提供的 XA 事務(wù)連接池,才能支持 XA 的事務(wù)。Apache ShardingSphere 在整合 XA 事務(wù)時,采用分離 XA 事務(wù)管理和連接池管理的方式,做到對應(yīng)用程序的零侵入。 三階段提交協(xié)議由于前文提到的兩階段提交協(xié)議的種種弊端,研究者們后來又提出了一種新的分布式原子提交協(xié)議:三階段提交協(xié)議(three-phase commit protocol, 3PC)。 三階段提交協(xié)議是對兩階段提交協(xié)議的擴(kuò)展,它在特定假設(shè)下避免了同步阻塞的問題。該協(xié)議基于以下兩個假設(shè): 基于這兩個假設(shè),三階段提交協(xié)議通過引入超時機(jī)制和一個額外的階段來解決阻塞問題,三階段提交協(xié)議把兩階段提交協(xié)議的第一個階段拆分成了兩步:1) 評估,2) 資源對象加鎖,最后才真正提交: 事實上,在最后階段,協(xié)調(diào)者不是通過追加本地日志的方式記錄提交決定的,而是首先保證讓至少 K 個參與者節(jié)點(diǎn)知道它決定提交該分布式事務(wù)。如果協(xié)調(diào)者發(fā)生故障了,那么剩下的參與者節(jié)點(diǎn)會重新選舉一個新的協(xié)調(diào)者,這個新的協(xié)調(diào)者就可以在集群中不超過 K 個參與者節(jié)點(diǎn)故障的情況下學(xué)習(xí)到舊協(xié)調(diào)者之前是否已經(jīng)決定要提交分布式事務(wù),若是,則重新開始協(xié)議的第三階段,否則就中止該事務(wù),重新發(fā)起分布式事務(wù)。 在最后的 DoCommit 階段,如果參與者一直沒有收到協(xié)調(diào)者的 DoCommit 或者Abort 請求消息時,會在等待超時之后,直接提交事務(wù)。這個決策機(jī)制是基于概率學(xué)的:當(dāng)已經(jīng)進(jìn)入第三階段之后,說明參與者在第二階段已經(jīng)收到了 PreCommit 請求消息,而協(xié)調(diào)者發(fā)出 PreCommit 請求的前提條件是它在第二階段開頭收集到的第一階段向所有參與者發(fā)出的 CanCommit 請求消息的反饋消息都是 YES。所以參與者可以根據(jù)自己收到了 PreCommit 請求消息這一既定事實得出這樣的一個結(jié)論:其他所有參與者都同意了進(jìn)行這次的事務(wù)執(zhí)行,因此當(dāng)前的參與者節(jié)點(diǎn)有理由相信,進(jìn)入第三階段后,其他參與者節(jié)點(diǎn)的本地事務(wù)最后成功提交的概率很大,而自己遲遲沒有收到 DoCommit 或 Abort 消息可能僅僅是因為網(wǎng)絡(luò)抖動或異常,因此直接提交自己的本地事務(wù)是一個比較合理的選擇。 |
|