方案核心:TCC+事務(wù)消息+異常日志+人工介入
1 前言
分布式事務(wù)是分布式領(lǐng)域一大神坑。
架構(gòu)即取舍、衡量利弊,世上沒有完美的方案。無需過多轉(zhuǎn)牛角尖,無需糾結(jié),以免進入死胡同。
1.1什么是分布式事務(wù)
簡單的講,N個服務(wù)調(diào)用,要么同時成功,要么同時失敗。
簡單的講,垮N個庫,要么同時成功,要么同時失敗。
數(shù)據(jù)庫事務(wù)-》強一致性
分布式事務(wù)-》性能、可用性方面的考量,最終一致性
分布式環(huán)境中通信存在三種狀態(tài):success failure timeout,超時是最麻煩的
最基本要求:事務(wù)參與者要實現(xiàn)冪等性,簡單的講,就是多次提交依然能夠達到同樣的目的,數(shù)據(jù)狀態(tài)的預(yù)期都是一致的
怎么實現(xiàn)冪等性?說白了就是去重,可借助業(yè)務(wù)id來進行校驗,可用的工具有redis、db
1.2什么場景下需要分布式事務(wù)
能不用盡量不用。
2 實現(xiàn)分布式事務(wù)的幾種方案
2.1 2PC
數(shù)據(jù)庫層面,強一致性
第一階段:協(xié)調(diào)者向所有事務(wù)參與方詢問,都可以提交嗎?所有參與方鎖定資源,如果全部say yes,則想所有參與方發(fā)布提交命令。否則發(fā)布rollback命令。
第二階段:大家一起提交或回滾
同步問題:大家都處于阻塞狀態(tài),協(xié)調(diào)者掛了,故障轉(zhuǎn)移,需要阻塞的時間更長
超時問題:如果參與方超時了,還沒接收到協(xié)調(diào)者的指令怎么辦?傻傻的原地等待?這樣也可能導(dǎo)致不一致
2.2 3PC
為了解決2PC階段存在的超時、宕機等問題,加入一個precommit階段,如果超時,則自動提交。方案基本用不到,不過多啰嗦展開。
2.3 TCC
Try-Confirm-Cancel的簡稱,是一種達到最終一致性的補償性事務(wù),跟2PC有點像,TCC針對的是業(yè)務(wù)層面,并非數(shù)據(jù)庫層面。
在try階段只是保留資源,執(zhí)行業(yè)務(wù)鎖定;
confirm階段:當(dāng)發(fā)起者收到所有請求者try success應(yīng)答,向協(xié)調(diào)者發(fā)出confirm。
如果出現(xiàn)超時,或者failure響應(yīng)的,則發(fā)出cancel指令。
協(xié)調(diào)者負(fù)責(zé)統(tǒng)一協(xié)調(diào)所有事務(wù)參與者進行confirm、cancel指令。
缺點:狀態(tài)復(fù)雜,導(dǎo)致在業(yè)務(wù)開發(fā)過程中,復(fù)雜度上升,狀態(tài)處理更麻煩。
協(xié)調(diào)者是一個關(guān)鍵角色,怎么保證協(xié)調(diào)者是高可用?
2.4 saga
該中心節(jié)點,即協(xié)調(diào)器知道整個事務(wù)的分布狀態(tài),相比于無中心節(jié)點方式,該方式有著許多優(yōu)點:
能夠避免事務(wù)之間的循環(huán)依賴關(guān)系。
參與者只需要執(zhí)行命令 / 回復(fù) (其實回復(fù)消息也是一種事件消息),降低參與者的復(fù)雜性。
開發(fā)測試門檻低。
在添加新步驟時,事務(wù)復(fù)雜性保持線性,回滾更容易管理。因此大多數(shù) saga 模型實現(xiàn)均采用了這種思路。
總結(jié)一下:SAGA 模型的優(yōu)點在于其降低了事務(wù)粒度,使得事務(wù)擴展更加容易,同時采用了異步化方式提升性能。但是其缺點在于很多時候很難定義補償接口,回滾代價高,而且由于 SAGA 在執(zhí)行過程中采用了先提交后補償?shù)乃悸愤M行操作,所以單個子事務(wù)在并發(fā)提交時的隔離性很難保證。
2.5 事務(wù)消息
rocketmq4.3支持事務(wù)消息,簡單的講,就是保持本地事務(wù)與消息發(fā)送的原子性。再簡單的講,就是我本地事務(wù)做完了,消息一定100%發(fā)送出去。
1、發(fā)送方:先往half隊列發(fā)prepare消息
2、發(fā)送方:執(zhí)行本地事務(wù)
3、發(fā)送方:如果commit,就發(fā)生commit消息,下發(fā)給訂閱者;如果rollback就發(fā)生rollback消息,刪除prepare,不下發(fā)。
4、rocketmq:如果沒有接收到任何信息,可能超時啦,出了各種異常,咋辦?回查事務(wù)狀態(tài),有可能發(fā)送方實例已經(jīng)宕機,需要回查同一個生產(chǎn)者組的其他實例來獲取狀態(tài),具體怎么獲取?參考rocketmq的事務(wù)消息示例代碼即可。
5、consumer段消息成功機制保障
對于消費者集群執(zhí)行本地事務(wù)失敗的情況,阿里提供給我們的解決方法是:人工解決
按照事務(wù)的流程,因為某種原因事務(wù)失敗,那么需要回滾整個流程。如果消息系統(tǒng)要實現(xiàn)這個回滾流程的話,系統(tǒng)復(fù)雜度將大大提升,且很容易出現(xiàn)Bug,估計出現(xiàn)Bug的概率會比消費失敗的概率大很多。
事務(wù)消息一定程度上也實現(xiàn)了分布式事務(wù),適用于上游業(yè)務(wù)不需要下游響應(yīng)的場景。發(fā)給事務(wù)消息就完事了。訂閱者根據(jù)消息做下游業(yè)務(wù)。失敗時人工介入。
2.6 分布式數(shù)據(jù)庫
解決分布式事務(wù)的最好方案就是不解決,交給分布式數(shù)據(jù)庫,天下大事,分久必合合久必分。以前大家費了老大勁把數(shù)據(jù)庫拆開,現(xiàn)在技術(shù)條件逐漸成熟。陸續(xù)出現(xiàn)了幾個分布式數(shù)據(jù)庫,首推google的spanner全球分布式數(shù)據(jù)庫,天然支持分布式事務(wù)。
基于google F1 spanner論文實現(xiàn)的分布式數(shù)據(jù)庫有:TIDB、cockroachdb發(fā)展的都不錯,1w+star可以持續(xù)關(guān)注,在國內(nèi)也有諸多落地實現(xiàn)的。
3 TCC方案詳解
3.1 角色
發(fā)起者、協(xié)調(diào)者、參與者

在上述例子中,bookingprocess即為發(fā)起者A,swiss B、easyjet C為參與者。
3.2 各個角色職責(zé)
發(fā)起者負(fù)責(zé)try,根據(jù)結(jié)果,向協(xié)調(diào)者發(fā)送confirm、cancel指令。
參與者實現(xiàn)try、confirm、cancel三個接口;
實現(xiàn)冪等性;
超時自動cancel。
協(xié)調(diào)者保證高可用,接收發(fā)起者的指令,根據(jù)情況發(fā)起confirm 、cancel,失敗重試。
3.3 主流程
1、外部前端調(diào)用booking A,A調(diào)B\C服務(wù)的try接口,
2、B\C服務(wù)返回response給A,有幾種情況:所有響應(yīng)均為success;響應(yīng)含有failure;B、C調(diào)用失敗,不可用,或者超時。
3、如果所有請求均為success,則向協(xié)調(diào)者發(fā)出confirm指令,大家一起提交。消息中包含了B\C服務(wù)的confirm URL。
4、如果響應(yīng)包含failure,則向協(xié)調(diào)者發(fā)出cancel指令,大家一起回滾。
4 TCC進化
4.1 TCC的缺陷
1、協(xié)調(diào)器如何保證高可靠,怎么解決協(xié)調(diào)器的單點故障?怎么解決服務(wù)宕機、網(wǎng)絡(luò)抖動,超時等問題
2、開發(fā)復(fù)雜,參與者需要提供try、confirm、cancel接口
3、如果所有的可靠性措施都失靈了,還是有異常情況,怎么處理?
4、resttcc對接口做了一些約束,并不符合實際要求,例如用httpcode來標(biāo)識各種狀態(tài),而很多項目團隊一般在數(shù)據(jù)包中定義返回狀態(tài)。
4.2 rocketmq事務(wù)消息登場
TCC方案中的交互模式皆為rest接口,在性能、可靠性方面不如消息的發(fā)布訂閱模式,rocketmq4.3支持事務(wù)消息,那么我們可以讓rocketmq擔(dān)任協(xié)調(diào)者的角色。
當(dāng)然你首先要先能玩轉(zhuǎn)rocketmq,能保證高可用,不是隨便搭個玩玩就行了。
怎么改進?
一、try階段還是rest調(diào)用。
事務(wù)發(fā)起者發(fā)送兩條事務(wù)消息一條消息體為commit,另一條為cancel,在接收到所有參與者的response后,根據(jù)返回的狀態(tài)進行相應(yīng)處理:
1、如果所有的參與者都返回success(怎么樣標(biāo)識success大家遵循統(tǒng)一的約定),標(biāo)識“commit”事務(wù)消息的狀態(tài)為commit,cancel直接丟棄即可
2、如果含有failure,則標(biāo)識“cancel”事務(wù)消息的狀態(tài)為commit,則cancel事務(wù)消息生效,另一臺直接丟棄
3、如果超時了,或者發(fā)起者當(dāng)前實例宕機等等異常因素,這時候不用處理都可以,大家想想為什么?
二、confirm階段
各個參與者無需實現(xiàn)confirm、cancel restfu接口
需要去訂閱事務(wù)消息隊列,監(jiān)聽commit或者cancel消息
如果訂閱到commit消息,那就是標(biāo)識try階段,大家都已經(jīng)成功鎖定資源了。那就大家都來commit吧
流程走到這里,大家都已經(jīng)鎖定資源的前提下,commit出現(xiàn)異常的概率已經(jīng)非常低了。
但是如果真的出現(xiàn)異常,commit失敗怎么辦?怎么辦?怎么辦?
再通知發(fā)起者,再發(fā)起cancel消息,再大家一起cancel,如果有人cancel失敗怎么辦?怎么辦?怎么辦?
死循環(huán)了,果斷跳出這個定時思維,這時候需要記錄異常日志,人工介入處理。不要什么都自動化,架構(gòu)即取舍,孰重孰輕,掂量一下就知道了。
三、異常日志體系
服務(wù)如果commit失敗,或者超時自動cancel等等,都記錄異常日志到業(yè)務(wù)數(shù)據(jù)庫,同時把異常日志推送到kafka->elk,結(jié)合鏈路監(jiān)控日志、應(yīng)用日志、業(yè)務(wù)異常日志進行綜合處理,人工介入。
四、cancel階段
如果訂閱到cancel消息,那就表明發(fā)起者讓大家一并cancel,釋放資源,做業(yè)務(wù)補償處理
cancel完,把日志推送到kafka-》elk-》人工綜合分析
4.3 異常日志體系的建立
方案可以采用業(yè)務(wù)異常日志(業(yè)務(wù)DB存儲一份)+kafka+ELK。
關(guān)聯(lián)聚合分析:根據(jù)traceid進行聚合分析,異常日志+鏈路監(jiān)控+應(yīng)用日志。
以便人工介入做最后的一環(huán)保障
參考資料
TCC英文文檔、github開源TCC方案、ACID、CAP原理、rocketmq4.3事務(wù)消息的實現(xiàn)機制https://mp.weixin.qq.com/s/rE6l2iWY2lGxDCcog7Muvw