一致 先验分布 后验分布_分布式事务常见解决方案与最终一致性
文章目錄
分布式事務產生背景
數據庫水平拆分
微服務拆分
分布式事務解決方案
TCC
XA事務(2PC)
最終一致性(消息事務)
錯誤場景一
錯誤場景二
錯誤場景三
解決方案對比
最終一致性-本地消息表
最終一致性-RocketMq事務消息
兩階段提交
事務狀態定時回查
事務消息核心
最后
參考資料
分布式事務產生背景?
數據庫水平拆分
業務發展初期,許多業務采用單庫單表的方案來存儲數據。隨著業務的不斷演進、發展,存儲數據量不斷變大,寫入QPS不斷增加,單庫的存儲、寫入都會存在瓶頸。因此我們對數據庫進行水平拆分,即將完整的數據集,通過一定規則(比如按userId,orderId取模),將表中的某些行切分到一個數據庫,而另外的某些行又切分到其他的數據庫中。如下圖所示
數據庫水平拆分后,引來了如下幾個問題
1. 跨庫查詢較為復雜,如跨庫join性能差
2. 原本往一個數據庫中寫入數據,現在可能變為往多個庫中同時寫入數據,這就是跨庫分布式事務問題
微服務拆分
在早期的一些系統中,不同業務模塊耦合在一個大的業務系統里,隨著業務的發展,單個業務系統擴展性差,耦合度高的缺點越來越明顯。因此將不同業務模塊進行拆分,構建為松耦合、可獨立部署的一組服務。不同業務系統間通過諸如Dubbo、Spring Cloud等微服務框架進行通信。
如圖所示,拆分后的業務應用,是一個單一的、可獨立部署的組件,不同業務服務之間松耦合,可拓展。
隨著服務的拆分,分布式事務問題也不可避免,服務拆分之前,在單JVM進程中可以通過Spring事務保證對單庫、多個業務表的操作在一個事務中。業務系統按照服務拆分之后,一個完整的業務操作往往需要通過Rpc調用多個服務,如何保證多個服務間的數據一致性成為一個難題
分布式事務解決方案
TCC
TCC模式分布式事務解決方案,需要用戶根據自己的業務場景實現 Try、Confirm 和 Cancel 三個操作,事務發起方在一階段執行 Try 方式,在二階段提交執行 Confirm 方法,二階段回滾執行 Cancel 方法。
是一種應用層面侵入業務的兩階段提交,每個操作含義如下
操作方法  | 含義  | 
Try  | 完成數據檢查(一致性),預留資源(隔離性)  | 
Confirm  | 確認執行業務操作,只使用try階段預留的資源,需保證冪等  | 
Cancel  | 取消業務操作,釋放預留資源,需保證冪等  | 
TCC模式的架構圖如下
執行步驟如下:
1. 主業務服務負責發起并完成整個事務操作。
2. 從業務服務提供TCC型業務操作。
3. 業務活動管理器(事務協調者)控制業務活動的一致性,它登記業務活動中的操作,并在業務活動提交時進行confirm操作,在業務活動取消時進行cancel操作,協調者會通過不斷重試的機制保障從業務的commit/cancel操作一定會執行,故需保證TCC接口的冪等性
TCC方案也有不足之處,表現在以下幾個方面:
1. 對應用的侵入性強。業務邏輯的每個分支都需要實現try、confirm、cancel三個操作,應用侵入性較強,改造成本高。
2. 實現難度較大。需要按照網絡狀態、系統故障等不同的失敗原因實現不同的回滾策略。為了滿足一致性的要求,confirm和cancel接口必須實現冪等
3. 需要注意極端情況下的空回滾、防懸掛控制,對開發者/TCC中間件有較高要求
市面上的TCC中間件有:tcc-transaction
XA事務(2PC)
XA事務利用事務資源(比如Oracle、DB2這些商業數據庫)對 XA 協議的支持,以 XA 協議的機制來管理分支事務的一種事務模式,需要引入事務管理器作為分布式事務的全局協調者
如圖所示,XA協議是一種特殊的兩階段提交協議,分為兩個階段
1. 執行階段:
可回滾:業務 SQL 操作放在 XA 分支中進行,由資源對 XA 協議的支持來保證可回滾
持久化:XA 分支完成后,執行 XA prepare,同樣,由資源對 XA 協議的支持來保證持久化(即,之后任何意外都不會造成無法回滾的情況)
2. 完成階段:
分支提交:執行 XA 分支的 commit分支回滾:執行 XA 分支的 rollback
缺點:XA目前在商業數據庫支持的比較理想,在mysql數據庫中支持的不太理想
目前市面上也出現了支持XA事務編程模型的中間件:Seata
同時Seata還支持一種特殊定制化,基本零侵入業務的AT模式(一種演進版本的2PC模式)
最終一致性(消息事務)
大部分的業務場景中,其實并不一定非得強求數據的強一致性,比如用戶在A系統進行簽到,那么在B系統里進行計算績效,只需保證A簽到成功后,B系統績效一定會計算,至于什么時候計算,是否立馬計算,其實并不重要。
其他諸如用戶在A系統下了單,在B系統用戶的積分一定得增加這種場景都是類似的,最終一致性也是解決分布式事務的一種常見方案。
很多場景下,我們“發消息”這個過程,目的往往是通知另外一個系統或者模塊去更新數據,消息隊列中的“事務”,主要解決消息生產者和消息消費者的數據一致性問題。
消息方案從本質上講是將分布式事務轉換為多個本地事務,然后依靠下游業務的重試機制達到最終一致性。基于消息的最終一致性方案性能比XA好很
錯誤場景一
看起來,好像消息事務就是這么點東西?上游更新完數據,發個消息讓下游消費下,下游也更新下自己的數據,這不就完了?
如圖所示上游肯定要保證上游數據的處理和消息的發送在一個事務里,常見的偽代碼如下
@Transactionalpublic void execute(ActionExecuteParam param) { //更新本地數據庫 mapper.update(param); //發送mq消息通知下游更新數據 mqProducer.sendMsg(msg);}上面的代碼對應到執行流程圖,即為如下所示,其中step3是容易被忽略的一步,如果把數據更新和發送mq消息放在一個事務里,那么實際數據的commit操作會在整個方法結束后(也就是消息完成后)進行,假如此時數據庫操作出現報錯、超時,那么消息已經發出去了,下游數據已經更新,但是上游數據卻沒有更新,出現了不一致
錯誤場景二
那么不加事務呢,行不行?如圖所示
此時假設step1數據更新完成后,step2進行消息發送的時候,失敗了,或者超時異常了,那么step1的數據不會進行回滾(數據已經commit),此時下游數據未更新,一樣出現了不一致的情況
錯誤場景三
先發消息,再更新數據庫行不行?
這種Case就更明顯了,如果消息發送成功了,但是這時候系統重啟、數據庫超時,都有可能導致消息發送成功(下游數據隨之更新),但是上游數據未更新,同樣會出現數據不一致
所以關鍵點是什么?只要上游執行的數據變更操作和發送消息不是一個原子操作,即不在一個事務中完成,那么,無論先后順序如何,如何操作,都會出現數據不一致性問題
解決方案對比
用表格的形式總結下上面介紹的幾種分布式事務解決方案優缺點
事務方案  | 優點  | 缺點  | 
2PC(XA事務)  | 實現簡單  | 1、需要數據庫(一般是XA支持) 2、鎖粒度大,性能差  | 
2PC(Seata ?AT模式)  | 實現簡單,業務侵入極低  | 需要引入、部署單獨Seata服務,維護成本高  | 
TCC  | 鎖粒度小,性能好  | 需要侵入業務,實現較為復雜,復雜業務實現冪等有難度  | 
消息事務  | 業務侵入小,無需編寫業務回滾補償邏輯  | 事務消息實現難度大,強依賴第三方中間件可靠性  | 
考慮到消息中間件是平時開發中必不可少的中間件,同時大部分業務場景下并不要求分布式事務的強一致性,因此下面重點介紹基于消息中間件如何實現分布式事務的最終一致性
最終一致性-本地消息表
本地消息表的設計核心是將需要分布式處理的任務通過消息日志的方式來異步執行。消息發送日志(記錄)可以存儲到本地文本、數據庫,再通過定時器自動或人工發起重試
即保證業務數據更新成功的同時,一定會有一條對應的消息記錄(消息發送狀態為待發送)在數據庫中,然后上游所在系統單獨啟動一個定時器去掃描該消息表,并將狀態為待發送的消息,投遞到消息服務器中,失敗重試,直到消息發送成功,那么就能解決數據更新和消息發送的原子性問題
整體流程圖如下
如果開發中使用的消息中間件并不支持事務消息的功能,那么本地消息表是一種不錯的最終一致性解決方案,那么缺點又是什么?顯而易見
業務方需要單獨設計消息表,及定時發送消息的定時器,增加了與業務無關的開發負擔
最終一致性-RocketMq事務消息
RocketMq 4.3版本中開源了事務消息,開發者可以借此來實現簡單的最終一致性。介紹事務消息之前,先拋出兩個核心概念:兩階段提交、事務狀態定時回查。
兩階段提交
關于兩階段提交的基本概念,貼上一張圖來簡單說明
因為消息發送是一個遠程調用,由于網絡的不穩定,無法和本地事務的執行處于一個原子操作中,針對這個缺點,RocketMQ基于兩階段提交協議做了如下改動
l?? 第一階段:生產者向MQ服務器發送事務消息(prepare半消息),服務端確認后回調通知生產者執行本地事務(此時消息為Prepare消息,存儲于RMQ_SYS_TRANS_HALF_TOPIC隊列中,不會被消費者消費)
l?? 第二階段:生產者執行完本地事務后(業務執行完成,同時將消息唯一標記,如transactionId與該業務執行記錄同時入庫,方便事務回查),根據本地事務執行結果,返回Commit/Rollback/Unknow狀態碼
1、服務端若收到Commit狀態碼,則將prepare消息變為提交(正常消息,可被消費者消費)
2、收到Rollback則對消息進行回滾(丟棄消息)
3、若狀態為Unknow,則等待MQ服務端定時發起消息狀態回查,超過一定重試次數或者超時,消息會被丟棄
引用一張流程圖來說明消息事務的兩階段提交
其中prepare半消息是事務消息的核心,正常情況下生產者投遞到Broker的消息(除了延遲消息),會立馬被消費者消息,而事務消息中,需要等待生產者執行完本地事務后,才真正對半消息進行投遞,這也就意味著,發送到Broker端的prepare半消息是不會被消費者立馬消費到的,為什么呢?
事務消息在Broker端進行存儲落盤到CommitLog的時候,會有如下2點特殊處理
修改消息topic為RMQ_SYS_TRANS_HALF_TOPIC,并備份消息原有topic,供后續commit消息時還原消息topic使用
修改消息queueId為0,并備份消息原有queueId,供后續commit消息時還原消息queueId使用
修改完topic和queueId后,事務消息也會像普通消息一樣存儲在commitLog中
看到這,是不是就明白,為什么prepare消息在發送后不會被立馬消費?因為消息topic被修改了
事務狀態定時回查
在第二階段中,生產者在本地事務執行完成后,需要向MQ服務器返回響應狀態碼,發送狀態碼的過程也是通過Netty發送網絡請求,假設由于網絡原因發送失敗怎么辦?本地事務已經提交/回滾了,但是Commit/Rollback狀態碼卻沒發出去,那么MQ服務器上這條prepare消息狀態豈不是無法被投遞/回滾
因此,MQ服務端會定時掃描存儲于RMQ_SYS_TRANS_HALF_TOPIC中的消息,若消息未被處理,則向消費發送者發起回調檢查,檢查消息對應本地事務執行狀態。從而保證消息事務狀態最終能和本地事務的狀態一致。上圖中的4、5、6就是MQ服務端定時回查步驟。
事務消息核心
RocketMq通過引入prepare半消息機制、事務消息回查機制,保證生產者消息的發送與本地事務的執行的原子性,將一個分布式大事務拆分成小事務,減少了系統間的交互。同時通過MQ 的高可用特性(不丟失),及At-Least-Once 特性確保正確投遞的事務消息會在下游一定被消費,從而保證數據的最終一致性。
最后
分布式事務作為平時開發中不可避免的一個技術難點,我們有必要了解其常見的解決方案。又因為消息中間件是開發中不可或缺的一個中間件,通過消息中間件來實現最終一致性是一種成本較低的可靠方案,希望本文介紹的基于消息中間件實現的最終一致性方案原理,對大家平時開發會有所幫助。至于具體的事務消息使用例子,大家可以參考RocketMq官網,由于篇幅原因,這里不再過多介紹。
同時,阿里開源的Seata,包含了上述常見的幾種分布式解決方案,如TCC、XA、AT(一種零侵入、業務層面實現補償、回滾的方案)、Saga(適用于長事務的最終一致性方案),具有較高的學習價值,有興趣的同學可以看看其實現原理。
參考資料
http://seata.io/zh-cn/index.html
https://github.com/apache/rocketmq
https://github.com/changmingxie/tcc-transaction
招聘時間:
普惠公眾平臺、carbo團隊持續招人,有興趣的可以投遞簡歷到:tomchenyin@didiglobal.com
職位描述
1、負責服務管理方向流程編排服務、任務平臺的開發與維護。
2、參與項目的系統分析,設計工作,承擔核心功能,公共核心架構模塊的代碼編寫。
3、解決各種疑難雜癥,系統優化,并且完成產品、平臺和組件的沉淀。
4、負責團隊穩定性建設工作。
任職要求
1、本科以上學歷,計算機軟件專業,3 年以上 JAVA 開發經驗。
2、JAVA 基礎扎實,熟悉 io/nio,多線程等基礎知識,熟悉分布式,緩存,消息隊列等主流技術。熟悉 spring 、mybatis 等主流框架,熟悉常用的設計模式。
3、具備較強的領域建模能力和業務 sense。
4、具備較強的抗壓能力和良好的溝通技巧,優秀的團隊合作精神,善于學習,深度思考。
總結
以上是生活随笔為你收集整理的一致 先验分布 后验分布_分布式事务常见解决方案与最终一致性的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: r语言做绘制精美pcoa图_R语言统计与
 - 下一篇: cdh用户权限_cdh设置hdfs权限