1 背景
在講述分布式事務的概念之前,我們先來回顧下事務相關的一些概念。
1.1 事務的基本概念
就是一個程序執行單元,里面的操作要么全部執行成功,要么全部執行失敗,不允許只成功一半另外一半執行失敗的事情發生。例如一段事務代碼做了兩次數據庫更新操作,那么這兩次數據庫操作要么全部執行成功,要么全部回滾。
1.2 事務的基本特性
我們知道事務有 4 個非常重要的特性,即我們常說的(ACID)。
- Atomicity(原子性):一個事務(transaction)中的所有操作,要么全部完成,要么全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被恢復(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。
- Consistency(一致性):在事務開始之前和事務結束以后,數據庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設規則,這包含資料的精確度、串聯性以及后續數據庫可以自發性地完成預定的工作。
- Isolation(隔離性):數據庫允許多個并發事務同時對其數據進行讀寫和修改的能力,隔離性可以防止多個事務并發執行時由于交叉執行而導致數據的不一致。事務隔離分為不同級別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重復讀(repeatable read)和串行化(Serializable)。
- Durability(持久性):事務處理結束后,對數據的修改就是永久的,即便系統故障也不會丟失
2 分布式事務
其實分布式事務從實質上看與數據庫事務的概念是一致的,既然是事務也就需要滿足事務的基本特性(ACID),只是分布式事務相對于本地事務而言其表現形式有很大的不同。
本地事務的時代,如果需要同時操作數據庫的多條記錄,而這些操作可以放到一個事務中,那么我們可以通過數據庫提供的事務機制就可以實現。
而隨著微服務架構的推進,原本一個本地邏輯執行單元,被拆分到了多個獨立的微服務中,這些微服務又分別操作了不同的數據庫和表。
比如下個指派個體的運輸任務,下運輸任務的同時要生成需求、計劃、任務,還要去調用詢價服務,投保服務,那么,一旦產生任何一個服務異常,都會產生事務性的問題。雖然對于我們現有邏輯來說,可以由運營作廢,但未來自動化之后呢?
分布式事務是為了解決微服務架構(形式都是分布式系統)中不同節點之間的數據一致性問題。這個一致性問題本質上解決的也是傳統事務需要解決的問題,即一個請求在多個微服務調用鏈中,所有服務的數據處理要么全部成功,要么全部回滾。當然分布式事務問題的形式可能與傳統事務會有比較大的差異,但是問題本質是一致的,都是要求解決數據的一致性問題。
而分布式事務的實現方式有很多種,最具有代表性的是由 Oracle Tuxedo 系統提出的 XA 分布式事務協議。XA 協議包括兩階段提交(2PC)和三階段提交(3PC)兩種實現,接下來我們分別來介紹下這兩種實現方式的原理。
3 兩階段提交(2PC)
兩階段提交又稱 2PC(two-phase commit protocol),2PC 是一個非常經典的強一致、中心化的原子提交協議。這里所說的中心化是指協議中有兩個角色:一個是分布式事務協調者(coordinator)和 N 個參與者(participant)。
3.1 2PC 運行原理
兩階段提交,顧名思義就是要進行兩個階段的提交:第一階段,準備階段(投票階段);第二階段,提交階段(執行階段)。
3.1.1 準備階段(Prepare phase)
- 分布式事務的發起方,向分布式事務協調者(Coordinator,也可以叫事務管理 TransactionManager)發送請求,
- Coordinator 分別向參與者(Participant)A、參與者(Participant)B 分別發送事務預處理請求,稱之為 Prepare,有些資料也叫”Vote Request”。
- 此時這些參與者節點一般來說就會打開本地數據庫事務,然后開始執行數據庫本地事務,每個數據庫參與者在本地執行事務并寫本地的 Undo/Redo 日志(Undo 日志是記錄修改前的數據,用于數據庫回滾,Redo 日志是記錄修改后的數據,用于提交事務后寫入數據文件),但在執行完成后并不會立馬提交數據庫本地事務,而是先向 Coordinator 進行 “Vote Commit” 的反饋,告知處理結果。
- 如果所有的參與者都向協調者做了 “Vote Commit” 的反饋的話,那么流程進入第二個階段。
3.1.2 提交階段(commit phase)
1)如果所有參與者均反饋的是成功,協調者就會向所有參與者發送 “全局提交確認通知(global_commit)”,參與者 Participant 就會完成自身本地數據庫事務的提交,并將提交結果回復 “ack” 消息給協調者 Coordinator,然后協調者 Coordinator 就會向調用方返回分布式事務處理完成的結果。如果有任何一個參與者返回失敗,則回滾事務。
2)如果參與者向協調者反饋 “Vote_Abort” 消息,即返回了失敗的消息。此時分布式事務協調者 Coordinator 就會向所有的參與者 Participant 發起事務回滾的消息(“global_rollback”),此時各個參與者就會回滾本地事務,釋放資源,并且向協調者發送 “ack” 確認消息,協調者就會向調用方返回分布式事務處理失敗的結果。
以上就是兩階段提交的基本過程了,那么按照這個兩階段提交協議,分布式系統的數據一致性問題就能解決么?
3.2 2PC 存在的問題
其實,2PC 只是通過增加了事務協調者(Coordinator)的角色來通過 2 個階段的處理流程來解決分布式系統中一個事務需要跨多個服務的數據一致性問題。
以下幾點是 XA - 兩階段提交協議中會遇到的一些問題:
- 性能問題:2PC 中的所有的參與者節點都為事務阻塞型,當某一個參與者節點出現通信超時,其余參與者都會被動阻塞占用資源不能釋放。
- 協調者單點故障問題:由于嚴重的依賴協調者,一旦協調者發生故障,而此時參與者還都處于鎖定資源的狀態,無法完成事務 commit 操作。雖然協調者出現故障后,會重新選舉一個協調者,可無法解決因前一個協調者宕機導致的參與者處于阻塞狀態的問題。
- 網絡閃斷導致腦裂:第二階段中協調者向參與者發送 commit 命令之后,一旦此時發生網絡抖動,導致一部分參與者接收到了 commit 請求并執行,可其他未接到 commit 請求的參與者無法執行事務提交。進而導致整個分布式系統出現了數據不一致。
4 三階段提交(3PC)
三階段提交又稱 3PC,在 2PC 的基礎上增加了 CanCommit 階段,并引入了超時機制。一旦事務參與者遲遲沒有收到協調者的 Commit 請求,就會自動進行本地 commit,這樣相對有效地解決了協調者單點故障的問題。
4.1 3PC 運行原理
4.1.1 CanCommit 階段
- 協調者向參與者發出 CanCommit ,進行事務詢問操作,所有參與者都反饋 yes 后,才能進入下一個階段。(這一個階段時不鎖表,不像 2pc 第一個階段就開始鎖表,3pc 的階段一是為了先排除個別參與者不具備提交事務能力的前提下,而避免鎖表。)簡單來說就是檢查下自身狀態的健康性。
- 有任何一個參與者反饋的結果是 No,整個分布式事務就會中斷,協調者就會向所有的參與者發送 “abort” 請求。
4.1.2 PreCommit 階段
- 在階段一中,如果所有的參與者都返回 Yes 的話,那么就會進入 PreCommit 階段進行事務預提交。此時分布式事務協調者會向所有的參與者發送 PreCommit 請求,參與者收到后開始執行事務操作,并將 Undo 和 Redo 信息記錄到事務日志中。參與者執行完事務操作后(此時屬于未提交事務的狀態),就會向協調者反饋 “Ack” 表示已經準備好提交,并等待協調者的下一步指令。
- 有任何一個參與者反饋的結果是 No,或協調者在等待參與者節點反饋的過程中超時(2PC 中只有協調者可以超時,參與者沒有超時機制)。整個分布式事務就會中斷,協調者就會向所有的參與者發送 “abort” 請求。
4.1.3 DoCommit 階段
- 在階段二中如果所有的參與者都可以進行 PreCommit 提交,那么協調者就會從 “預提交狀態”->“提交狀態”。然后向所有的參與者發送”doCommit” 請求,參與者在收到提交請求后,執行事務提交操作,并向協調者反饋 “Ack” 消息,協調者收到所有參與者的 Ack 消息后完成事務。
- 同樣,如果有一個參與者節點未完成 PreCommit 的反饋或者反饋超時,那么協調者都會向所有的參與者節點發送 abort 請求,從而中斷事務。
相比較 2PC 而言,3PC 對于協調者(Coordinator)和參與者(Participant)都設置了超時時間,解決了參與者在長時間無法與協調者節點通訊(協調者掛掉了)的情況下,無法釋放資源的問題,因為參與者自身擁有超時機制會在超時后,自動進行本地 commit 從而進行釋放資源。而這種機制也側面降低了整個事務的阻塞時間和范圍。
另外,通過 CanCommit、PreCommit、DoCommit 三個階段的設計,相較于 2PC 而言,多設置了一個緩沖階段保證了在最后提交階段之前各參與節點的狀態是一致的。
3PC 的缺點:
3PC 在去除阻塞的同時也引入了新的問題,那就是參與者接收到 precommit 消息后,如果出現網絡分區,此時協調者所在的節點和參與者無法進行正常的網絡通信,在這種情況下,該參與者依然會進行事務的提交,這必然出現數據的不一致性。
5 補償事務(TCC)
TCC 與 2PC、3PC 一樣,只是分布式事務的一種實現方案。
5.1 TCC 原理:
TCC(Try-Confirm-Cancel)又稱補償事務。其核心思想是:” 針對每個操作都要注冊一個與其對應的確認和補償(撤銷操作)”。它分為三個操作:
- Try 階段:主要是對業務系統做檢測及資源預留,比如說凍結庫存。
- Confirm 階段:確認執行業務操作。
- Cancel 階段:取消執行業務操作。
TCC 事務的處理流程與 2PC 兩階段提交類似,不過 2PC 通常都是在跨庫的 DB 層面,而 TCC 本質上就是一個應用層面的 2PC,需要通過業務邏輯來實現。這種分布式事務的實現方式的優勢在于,可以讓應用自己定義數據庫操作的粒度,使得降低鎖沖突、提高吞吐量成為可能。
而不足之處則在于對應用的侵入性非常強,業務邏輯的每個分支都需要實現 try、confirm、cancel 三個操作。此外,其實現難度也比較大,需要按照網絡狀態、系統故障等不同的失敗原因實現不同的回滾策略。為了滿足一致性的要求,confirm 和 cancel 接口還必須實現冪等。
TCC 的具體原理圖如下:
5.2 注意事項:
1. 業務操作分兩階段完成:
接入 TCC 前,業務操作只需要一步就能完成,但是在接入 TCC 之后,需要考慮如何將其分成 2 階段完成,把資源的檢查和預留放在一階段的 Try 操作中進行,把真正的業務操作的執行放在二階段的 Confirm 操作中進行;
TCC 服務要保證第一階段 Try 操作成功之后,二階段 Confirm 操作一定能成功;
2. 允許空回滾;
事務協調器在調用 TCC 服務的一階段 Try 操作時,可能會出現因為丟包而導致的網絡超時,此時事務協調器會觸發二階段回滾,調用 TCC 服務的 Cancel 操作;
TCC 服務在未收到 Try 請求的情況下收到 Cancel 請求,這種場景被稱為空回滾;TCC 服務在實現時應當允許空回滾的執行;
3. 防懸掛控制;
事務協調器在調用 TCC 服務的一階段 Try 操作時,可能會出現因網絡擁堵而導致的超時,此時事務協調器會觸發二階段回滾,調用 TCC 服務的 Cancel 操作;在此之后,擁堵在網絡上的一階段 Try 數據包被 TCC 服務收到,出現了二階段 Cancel 請求比一階段 Try 請求先執行的情況;
用戶在實現 TCC 服務時,應當允許空回滾,但是要拒絕執行空回滾之后到來的一階段 Try 請求;
4. 冪等控制:
無論是網絡數據包重傳,還是異常事務的補償執行,都會導致 TCC 服務的 Try、Confirm 或者 Cancel 操作被重復執行;用戶在實現 TCC 服務時,需要考慮冪等控制,即 Try、Confirm、Cancel 執行次和執行多次的業務結果是一樣的;
5. 業務數據可見性控制;
TCC 服務的一階段 Try 操作會做資源的預留,在二階段操作執行之前,如果其他事務需要讀取被預留的資源數據,那么處于中間狀態的業務數據該如何向用戶展示,需要業務在實現時考慮清楚;通常的設計原則是 “寧可不展示、少展示,也不多展示、錯展示”;
6. 業務數據并發訪問控制;
TCC 服務的一階段 Try 操作預留資源之后,在二階段操作執行之前,預留的資源都不會被釋放;如果此時其他分布式事務修改這些業務資源,會出現分布式事務的并發問題;
用戶在實現 TCC 服務時,需要考慮業務數據的并發控制,盡量將邏輯鎖粒度降到最低,以最大限度的提高分布式事務的并發性;
6 Hmily
Hmily (How much I love you)
高性能分布式事務 tcc 開源框架。基于 JAVA 語言來開發(JDK1.8),支持 dubbo,springcloud,motan 等 rpc 框架進行分布式事務。
框架特性
- 支持嵌套事務 (Nested transaction support).
- 采用 disruptor 框架進行事務日志的異步讀寫,與 RPC 框架的性能毫無差別。
- 支持 SpringBoot-starter 項目啟動,使用簡單。
- RPC 框架支持 : dubbo,motan,springcloud。
- 本地事務存儲支持 : redis,mongodb,zookeeper,file,MySQL。
- 事務日志序列化支持 :java,hessian,kryo,protostuff。
- 采用 Aspect AOP 切面思想與 Spring 無縫集成,天然支持集群。
- 內置經典的分布式事務場景 demo 工程,并有 swagger-ui 可視化界面可以快速體驗。
6.1 Hmily 原理及流程圖
原理圖:
流程圖:
7 參考文獻
- https://dromara.org/website/zh-cn/docs/hmily/index.html
- https://houbb.Github.io/2018/10/30/hmily
- https://developer.aliyun.com/article/609854
- https://blog.csdn.NET/bjweimengshu/article/detAIls/86698036
- https://blog.csdn.net/u014296316/article/details/90185589
作者:京東物流 宋樂
來源:京東云開發者社區 自猿其說 Tech 轉載請注明來源