Apache RocketMQ-4.3.0正式Release了事務消息的特性,順着最近的這個熱點。第一篇文章,就來聊一下在軟件工程學上的長久的難題——分佈式事務(Distributed Transaction)。數據庫
這個技術也在各個諸如阿里,騰訊等大廠的內部,被普遍地實現,利用及優化。可是因爲理論上就有難點,因此分佈式事務就隱晦得成了大廠對於小廠的技術壁壘。相信來看這篇文章的同窗,必定都聽過不少關於分佈式事務的術語,比較二階段提交,TCC,最終一致性等,因此這裏也很少普及概念。bash
咱們直接上正題,利用RocketMQ設計本身的分佈式事務組件。網絡
舉個虛擬場景引出問題分佈式
用戶從農行
轉帳100元去招行
,農行的系統和招行的系統分別部署在本身的機房,系統之間經過消息
進行通訊,防止過分耦合。組件化
整個模型能夠不恰當得描述爲:農行扣了100元后,發送「已經扣款」的消息給招行,招行收到消息,知道農行扣款成功了,而後在招行帳戶上加100元。性能
問題是,農行這邊,方案1. 先扣100元再發消息,方案2. 先發消息再扣100元優化
整理下整個事務不一致的場景:this
方案1,spa
農行扣100後成功,可是消息發送失敗,招行沒有加100設計
方案2,
消息發送成功,可是農行扣100元失敗,招行收到消息加了100
各位同窗應該已經發現問題所在了,扣款和發送消息這兩個事情,沒有辦法經過調換順序實現「同時成功」,或者「同時失敗」。若是前者成功,後者失敗,就會形成不一致。
RocketMQ,如下簡稱RMQ,爲了實現事務消息引入了一種新的消息類型:TransactionMsg
一個完整的事務消息分紅兩個部分:
HalfMsg(Prepare)
+ Commit/RollbackMsg
Producer發送了HalfMsg後,因爲HalfMsg不是一個完整的事務消息,Consumer沒法馬上就消費到該消息,Producer能夠對HalfMsg進行Commit或者Rollback來終結事務(EndTransacaction)。只有當Commit了HalfMsg後,Consumer才能消費到這條消息。RMQ會按期去向Producer詢問,是否能夠Commit或者Rollback那些因爲錯誤沒有被終結的HalfMsg來結束它們的生命週期,以達成事務最終的一致。
依然是剛剛的轉帳場景,咱們用RMQ事務消息來優化下流程:
農行向RMQ同步發送HalfMsg,消息中攜帶農行即將要扣100元的信息
農行HalfMsg成功發送後,執行數據庫本地事務,在本身的系統中扣100元
農行查看本地事務執行狀況
本地事務返回成功,農行向RMQ提交(Commit)HalfMsg
招行系統訂閱了RMQ,順利收到農行已經扣款100元的信息
招行系統執行本地事務,在招行的系統中加100元
圖1:RMQ事務消息原理
一樣得,咱們逐個來分析下這個流程是否是會出現不一致:
農行發送HalfMsg是同步發送(Sync),若是HalfMsg發送不成功,壓根就不會執行本地事務
發送HalfMsg成功,可是農行扣款****本地事務失敗,也沒事,若是本地事務沒有成功,馬上就發送Rollback去回滾HalfMsg。就當以前啥事都沒有發生過
農行本地事務成功了,可是Commit卻失敗了,可是因爲HalfMsg已經在RMQ中,RMQ就能經過定時程序讓農行從新檢測本地事務是否成功,從新Commit。Rollback失敗了也是同理
招行消費了消息後,加錢本地事務失敗了,可是招行收到的消息持久化在MQ,甚至能夠持久化在招行數據庫,能夠進行事務重試
剛剛討論的案例是很是理想化的,整個分佈式事務中,只涉及到了金額的變化,可是,真正的線上系統,做爲消息發送方的本地事務可能就很是複雜,可能涉及到了幾十張不一樣的表,那RMQ用定時器來Check HalfMsg,難道去查下涉及該事務的每一張表的數據是否提交成功?顯然這種方案很是業務侵入很是大,而且很難組件化。因此須要在本地事務中設計一張Transaction表,將業務表和Transaction綁定在同一個本地事務中,若是農行的扣款本地事務成功時,Transaction中應當已經記錄該TransactionId的狀態爲「已完成」。當最後須要檢查時,只須要檢查對應的TransactionId的狀態是不是「已完成」就好,而不用關心具體的業務數據。
再談一個小細節,
細心的同窗可能發現,剛剛No.3的討論實際上是有點不嚴謹的,RMQ在調用Commit或者Rollback時,用的是Oneway
的方式,熟悉RMQ源碼的話,知道這種網絡調用是只單向發送Request,不會去獲取Response。消息發送性能上是有很是大的提高的,可是若是真的發送失敗,Producer是不會知曉的,最後只能經過定時檢查HalfMsg才能終結事務。
public void endTransactionOneway(
final String addr,
final EndTransactionRequestHeader requestHeader,
final String remark,
final long timeoutMillis
) throws RemotingException, MQBrokerException, InterruptedException {
RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.END_TRANSACTION, requestHeader);
request.setRemark(remark);
// 使用Oneway發送end transaction類型的
this.remotingClient.invokeOneway(addr, request, timeoutMillis);
}
複製代碼
不是全部的MQ都能支持事務消息,如何使用通常的MQ來搭建分佈式事務組件,甚至抽象成一個事務SOA服務?
其實仔細分析下RMQ的事務消息,咱們能夠把它拆解成兩個部分:
事務管理器
+ 消息
所謂的事務管理器,就是對於事務的預備(Prepare)、提交(Commit)和回滾(Rollback)的管理,另外還包含預備事務的定時檢查器。
消息,指的就是通常的同步消息,發送後能明確獲得發送結果,用於事務系統與業務系統解耦。幾乎全部的分佈式MQ都是支持這種消息的。
咱們來設計下本身的DistributedTransaction SOA,如下簡稱DT-SOA
圖2:分佈式事務服務化
流程仍是沒有變,但分佈式事務再也不強依賴RMQ,而是用通常的MQ代替:
系統A發送事務,首先調用DT-SOA的Prepare方法準備開啓事務,因爲是同步調用,獲取SendResult,若是發送成功,拿到全局分佈式事務的ID——TID
系統A用獲取到的TID執行本地事務,本地事務中包含Transaction狀態表,成功後將TID對應的狀態置爲「已完成」
系統A調用DT-SOA提交事務,DT-SOA用MQ發送同步消息給系統B
系統B監聽對應Topic,接收到消息後,執行對應的本地事務