微服務(wù)興起這幾年涌現(xiàn)出不少分布式事務(wù)框架综看,比如ByteTCC、TCC-transaction岖食、EasyTransaction以及最近很火爆的Seata红碑。最近剛看了Seata的源碼(v0.5.2),借機(jī)記錄一下自己對分布式事務(wù)的一些理解泡垃。(3年前這類框架還沒成熟析珊,項(xiàng)目需要自己也寫過一個柔性事務(wù)框架)
本文分五部分,首先明確分布式事務(wù)概念的演變蔑穴,然后簡單說下為什么大家不用XA忠寻,第三部分闡述兩階段提交的提升,第四部分介紹Seata的架構(gòu)的亮點(diǎn)與問題存和,第五部分談下分布式事務(wù)的取舍奕剃。 限于篇幅一些網(wǎng)上可搜索的細(xì)節(jié)本文不展開闡述。(例如XA捐腿、Saga纵朋、TCC、Seata等原理的的詳細(xì)介紹)
1.分布式事務(wù)的泛化
提起分布式事務(wù)茄袖,最早指的涉及多個資源的數(shù)據(jù)庫事務(wù)問題操软。
wiki對分布式事務(wù)的定義:A distributed transaction is a database transaction in which two or more network hosts are involved.
不過事務(wù)一詞含義隨著SOA架構(gòu)逐漸擴(kuò)大,根據(jù)上下文不同宪祥,可分為兩類:
- System transaction
- Business transaction
前者多指數(shù)據(jù)庫事務(wù)聂薪,后者則多對應(yīng)一個業(yè)務(wù)交易。與此同時蝗羊,分布式事務(wù)的含義也在泛化藏澳,尤其SOA、微服務(wù)概念流行起來后耀找,多指的是一個業(yè)務(wù)場景翔悠,需要編排很多獨(dú)立部署的服務(wù)時,如何保證交易整體的原子性與一致性問題涯呻,這類分布式事務(wù)也稱作長事務(wù)(long-lived transaction)凉驻,例如一個定行程的交易腻要,它由購買航班复罐、租車以及預(yù)訂酒店構(gòu)成,而航班預(yù)訂可能需要一兩天才能確認(rèn)雄家。為了統(tǒng)一對概念的理解效诅,本文默認(rèn)指的都是這類長事務(wù)。
分布式事務(wù)概念泛化的同時,也帶來了一個技術(shù)問題乱投,微服務(wù)下這類分布式事務(wù)的ACID該如何保證咽笼?是否仍然可以用傳統(tǒng)兩階段提交/XA去解決?很可惜戚炫,基于數(shù)據(jù)庫的XA有點(diǎn)像扶不起的阿斗剑刑,中看不中用。
2.為什么XA大家都不用双肤?
其實(shí)也并非不用施掏,例如在IBM大型機(jī)上基于CICS很多跨資源是基于XA協(xié)議實(shí)現(xiàn)的分布式事務(wù),XA也事實(shí)上算分布式事務(wù)處理的規(guī)范了茅糜,但在為什么互聯(lián)網(wǎng)中很少使用七芭,究其原因我覺得有幾個:
- 性能(阻塞性協(xié)議,增加響應(yīng)時間蔑赘、鎖時間狸驳、死鎖)
- 數(shù)據(jù)庫支持完善度(MySQL 5.7之前都有缺陷)
- 協(xié)調(diào)者依賴獨(dú)立的J2EE中間件(早期重量級Weblogic、Jboss缩赛,后期輕量級Atomikos耙箍、Narayana和Bitronix)
- 運(yùn)維復(fù)雜,DBA缺少這方面經(jīng)驗(yàn)
- 并不是所有資源都支持XA協(xié)議峦筒。
- 大廠懂所以不使用究西,小公司不懂所以不敢用
準(zhǔn)確講XA是一個規(guī)范、協(xié)議物喷,它只是定義了一系列的接口卤材,只是目前大多數(shù)實(shí)現(xiàn)XA的都是數(shù)據(jù)庫或者M(jìn)Q,所以提起XA往往多指基于資源層的底層分布式事務(wù)解決方案峦失。其實(shí)現(xiàn)在也有些數(shù)據(jù)分片框架或者中間件也支持XA協(xié)議扇丛,畢竟它的兼容性、普遍性更好尉辑。
3.兩階段提交的“提升”
基于數(shù)據(jù)庫的XA協(xié)議本質(zhì)上就是兩階段提交帆精,但由于性能原因在互聯(lián)網(wǎng)高并發(fā)場景下并不適用。如果數(shù)據(jù)庫只能保證本地ACID時隧魄,那么出現(xiàn)其中交易異常后卓练,如何實(shí)現(xiàn)整個交易原子性A,從而保證一致性C呢购啄?另外在處理過程中如何保證隔離性呢襟企?
最直接就是按照邏輯依次調(diào)用服務(wù),當(dāng)出現(xiàn)異常怎么辦?那就對那些已經(jīng)成功的進(jìn)行補(bǔ)償狮含,補(bǔ)償成功就一致了顽悼,這種樸素的模型就是Saga曼振。但Saga這種方式并不能保證隔離性,于是出現(xiàn)了TCC蔚龙,在實(shí)際交易邏輯前先做業(yè)務(wù)檢查冰评、對涉及到的業(yè)務(wù)資源進(jìn)行“預(yù)留”,或者說是一種“中間狀態(tài)”木羹,如果都預(yù)留成功則完成這些預(yù)留資源的真正業(yè)務(wù)處理甲雅,典型的如票務(wù)座位等場景。當(dāng)然還有像Ebay提出的基于消息表坑填,即可靠消息最終一致模型务荆,但本質(zhì)上這也屬于Saga模式的一種特定實(shí)現(xiàn),它的關(guān)鍵點(diǎn)有兩個:1.基于應(yīng)用共享事務(wù)記錄執(zhí)行軌跡穷遂,2.然后通過異步重試確保交易最終一致(這也使得這種方式不適用那些業(yè)務(wù)上允許補(bǔ)償回滾的場景)函匕。
這類分布式事務(wù)場景并不是微服務(wù)才出現(xiàn)的,在SOA時代其實(shí)就有了蚪黑,常見的Saga盅惜、TCC、可靠消息最終一致等模型也都是很多年前就有了忌穿,只是最近幾年隨著微服務(wù)興起抒寂,這些方案又重新被人關(guān)注了起來。
仔細(xì)對比這些方案與XA掠剑,會發(fā)現(xiàn)這些方案本質(zhì)上都是將兩階段提交從資源層提升到了應(yīng)用層屈芜。
? Saga的核心就是補(bǔ)償,一階段就是服務(wù)的正常順序調(diào)用(數(shù)據(jù)庫事務(wù)正常提交)朴译,如果都執(zhí)行成功井佑,則第二階段則什么都不做;但如果其中有執(zhí)行發(fā)生異常眠寿,則依次調(diào)用其補(bǔ)償服務(wù)(一般多逆序調(diào)用未已執(zhí)行服務(wù)的反交易)來保證整個交易的一致性躬翁。應(yīng)用實(shí)施成本一般。
? TCC的特點(diǎn)在于業(yè)務(wù)資源檢查與加鎖盯拱,一階段進(jìn)行校驗(yàn)盒发,資源鎖定,如果第一階段都成功狡逢,二階段對鎖定資源進(jìn)行交易邏輯宁舰,否則,對鎖定資源進(jìn)行釋放奢浑。應(yīng)用實(shí)施成本較高蛮艰。
? 基于可靠消息最終一致,一階段服務(wù)正常調(diào)用殷费,同時同事務(wù)記錄消息表印荔,二階段則進(jìn)行消息的投遞,消費(fèi)详羡。應(yīng)用實(shí)施成本較低
具體到基于這些模型實(shí)現(xiàn)的分布式事務(wù)框架仍律,也多借鑒了DTP(Distributed Transaction Processing)模型。
- RM負(fù)責(zé)本地事務(wù)的提交实柠,同時完成分支事務(wù)的注冊水泉、鎖的判定,扮演事務(wù)參與者角色窒盐。
- TM負(fù)責(zé)整體事務(wù)的提交與回滾的指令的觸發(fā)草则,扮演事務(wù)的總體協(xié)調(diào)者角色。
不同框架在實(shí)現(xiàn)時蟹漓,各組件角色的功能炕横、部署形態(tài)會根據(jù)需求進(jìn)行調(diào)整,例如TM有的是以jar包形式與應(yīng)用部署在一起葡粒,有的則剝離出來需要單獨(dú)部署(例如Seata中將TM的主要功能放到一個邏輯上集中的Server上份殿,叫做TC( Transaction Coordinator ))
4. Seata架構(gòu)得與失
今年初,阿里發(fā)布了開源分布式事務(wù)框架Fescar嗽交,后來跟螞蟻TCC方案整合后改名為Seata卿嘲,目前版本雖然只到0.6,但GitHub star已經(jīng)過9k夫壁,一方面可見阿里在圈內(nèi)推廣能力拾枣,另外一方面也說明大家對阿里分布式事務(wù)框架的期待。Seata的使用方式以及原理在其github wiki上已經(jīng)闡述的很清晰(https://github.com/seata/seata/wiki)盒让,網(wǎng)上也已有很多源代碼剖析的文章梅肤。接下來我們通過分析Seata AT模式原理,來看看它的亮點(diǎn)與問題邑茄。
Seata對MT以及TCC的支持亮點(diǎn)有限凭语,這兩種模式更多是為了兼容已有應(yīng)用生態(tài)。
Seata團(tuán)隊(duì)畫了一個的詳細(xì)調(diào)用流程圖撩扒,對照此圖閱讀其源碼會輕松很多似扔。
4.1 亮點(diǎn)
相比與其它分布式事務(wù)框架,Seata架構(gòu)的亮點(diǎn)主要有幾個:
- 應(yīng)用層基于SQL解析實(shí)現(xiàn)了自動補(bǔ)償搓谆,從而最大程度的降低業(yè)務(wù)侵入性炒辉;
- 將分布式事務(wù)中TC(事務(wù)協(xié)調(diào)者)獨(dú)立部署,負(fù)責(zé)事務(wù)的注冊泉手、回滾黔寇;
- 通過全局鎖實(shí)現(xiàn)了寫隔離與讀隔離。
這些特性的具體實(shí)現(xiàn)機(jī)制其官網(wǎng)以及github上都有詳細(xì)介紹斩萌,這里不展開介紹缝裤。
4.2 性能損耗
我們看看Seata增加了哪些開銷(純內(nèi)存運(yùn)算類的忽略不計):
一條Update的SQL屏轰,則需要全局事務(wù)xid獲取(與TC通訊)憋飞、before image(解析SQL霎苗,查詢一次數(shù)據(jù)庫)、after image(查詢一次數(shù)據(jù)庫)榛做、insert undo log(寫一次數(shù)據(jù)庫)唁盏、before commit(與TC通訊,判斷鎖沖突)检眯,這些操作都需要一次遠(yuǎn)程通訊RPC厘擂,而且是同步的。另外undo log寫入時blob字段的插入性能也是不高的锰瘸。每條寫SQL都會增加這么多開銷,粗略估計會增加5倍響應(yīng)時間(二階段雖然是異步的刽严,但其實(shí)也會占用系統(tǒng)資源,網(wǎng)絡(luò)避凝、線程港庄、數(shù)據(jù)庫)。
前后鏡像如何生成恕曲?
通過druid解析SQL鹏氧,然后復(fù)用業(yè)務(wù)SQL中的where條件,然后生成Select SQL執(zhí)行佩谣。
4.3 性價比
為了進(jìn)行自動補(bǔ)償把还,需要對所有交易生成前后鏡像并持久化,可是在實(shí)際業(yè)務(wù)場景下茸俭,這個是成功率有多高吊履,或者說分布式事務(wù)失敗需要回滾的有多少比率?這個比例在不同場景下是不一樣的调鬓,考慮到執(zhí)行事務(wù)編排前艇炎,很多都會校驗(yàn)業(yè)務(wù)的正確性,所以發(fā)生回滾的概率其實(shí)相對較低腾窝。按照二八原則預(yù)估缀踪,即為了20%的交易回滾,需要將80%的成功交易的響應(yīng)時間增加5倍虹脯,這樣的代價相比于讓應(yīng)用開發(fā)一個補(bǔ)償交易是否是值得驴娃?值得我們深思。
業(yè)界還有種思路循集,通過數(shù)據(jù)庫binlog恢復(fù)SQL執(zhí)行前后鏡像唇敞,這樣省去了同步undo log生成記錄,減少了性能損耗,同時對業(yè)務(wù)零侵入疆柔,個人感覺是一種更好的方式咒精。
4.4 全局鎖
4.4.1 熱點(diǎn)數(shù)據(jù)
Seata在每個分支事務(wù)中會攜帶對應(yīng)的鎖信息,在before commit階段會依次獲取鎖(因?yàn)樾枰獙⑺蠸QL執(zhí)行完才能拿到所有鎖信息旷档,所以放在commit前判斷)模叙。相比XA,Seata 雖然在一階段成功后會釋放數(shù)據(jù)庫鎖彬犯,但一階段在commit前全局鎖的判定也拉長了對數(shù)據(jù)鎖的占有時間,這個開銷比XA的prepare低多少需要根據(jù)實(shí)際業(yè)務(wù)場景進(jìn)行測試查吊。全局鎖的引入實(shí)現(xiàn)了隔離性谐区,但帶來的問題就是阻塞,降低并發(fā)性逻卖,尤其是熱點(diǎn)數(shù)據(jù)宋列,這個問題會更加嚴(yán)重。
4.4.2 回滾鎖釋放時間
Seata在回滾時评也,需要先刪除各節(jié)點(diǎn)的undo log炼杖,然后才能釋放TC內(nèi)存中的鎖,所以如果第二階段是回滾盗迟,釋放鎖的時間會更長坤邪。
4.4.3 死鎖問題
Seata的引入全局鎖會額外增加死鎖的風(fēng)險,具體可見https://github.com/seata/awesome-seata/blob/master/wiki/en-us/Fescar-AT.md罚缕,但如果出現(xiàn)死鎖艇纺,會不斷進(jìn)行重試,最后靠等待全局鎖超時邮弹,這種方式并不優(yōu)雅黔衡,也延長了對數(shù)據(jù)庫鎖的占有時間。
4.5 其它問題
- 對于部分采用Seata的應(yīng)用腌乡,如何保證數(shù)據(jù)不臟讀盟劫、幻讀?
Seata提供了一個@GlobalLock的注解与纽,可以提供輕量級全局鎖判定的功能(不生成undo log)侣签,但還是需要集成使用Seata。
- TC在邏輯上是單點(diǎn)急迂,如何做到高可用硝岗、高性能還是需要后續(xù)版本不斷優(yōu)化。
- 單機(jī)多數(shù)據(jù)源跨服務(wù)目前不支持袋毙。
5. 分布式事務(wù)的取舍
嚴(yán)格的ACID事務(wù)對隔離性的要求很高型檀,在事務(wù)執(zhí)行中必須將所有的資源鎖定, 對于長事務(wù)來說听盖,整個事務(wù)期間對數(shù)據(jù)的獨(dú)占胀溺,將嚴(yán)重影響系統(tǒng)并發(fā)性能裂七。 因此,在高并發(fā)場景中仓坞,對ACID的部分特性進(jìn)行放松從而提高性能背零,這便產(chǎn)生了BASE柔性事務(wù)。柔性事務(wù)的理念則是通過業(yè)務(wù)邏輯將互斥鎖操作從資源層面上移至業(yè)務(wù)層面无埃。通過放寬對強(qiáng)一致性要求徙瓶,來換取系統(tǒng)吞吐量的提升。另外提供自動的異臣党疲恢復(fù)機(jī)制侦镇,可以在發(fā)生異常后也能確保事務(wù)的最終一致。
基于XA的分布式事務(wù)如果要嚴(yán)格保證ACID织阅,實(shí)際需要事務(wù)隔離級別為SERLALIZABLE壳繁。
由上可見柔性事務(wù)需要應(yīng)用層進(jìn)行參與,因此這類分布式事務(wù)框架一個首要的功能就是怎么最大程度降低業(yè)務(wù)改造成本荔棉,然后就是盡可能提高性能(響應(yīng)時間闹炉、吞吐),最好是保證隔離性润樱。
一個好的分布式事務(wù)框架應(yīng)用盡可能滿足以下特性:
1. 業(yè)務(wù)改造成本低
2. 性能損耗低
3. 隔離性保證完整
但如同CAP渣触,這三個特性是相互制衡的,往往只能滿足其中兩個壹若,我們可以畫一個三角約束:
基于業(yè)務(wù)補(bǔ)償?shù)腟aga滿足1.2昵观;TCC滿足2.3;Seata滿足1.3舌稀。
當(dāng)然如果我們要自己設(shè)計一個分布式事務(wù)框架啊犬,還需要考慮很多其它特性,在明確目標(biāo)場景偏好后進(jìn)行權(quán)衡取舍壁查,這些特性包括但不限于以下:
- 業(yè)務(wù)侵入性(基于注解觉至、XML,補(bǔ)償邏輯)
- 隔離性(寫隔離/讀隔離/讀未提交睡腿,業(yè)務(wù)隔離/技術(shù)隔離)
- TM/TC部署形態(tài)(單獨(dú)部署语御、與應(yīng)用部署一起)
- 錯誤恢復(fù)(自動恢復(fù)、手動恢復(fù))
- 性能(回滾的概率席怪、付出的代價,響應(yīng)時間应闯、吞吐)
- 高可用(注冊中心、數(shù)據(jù)庫)
- 持久化(數(shù)據(jù)庫挂捻、文件碉纺、多副本一致算法)
- 同步/異步(2PC執(zhí)行方式)
- 日志清理(自動、手動)
...
結(jié)語
分布式事務(wù)一直是業(yè)界難題,難在于CAP定理骨田,在于分布式系統(tǒng)8大錯誤假設(shè)耿导,在于FLP不可能原理,在于我們習(xí)慣于單機(jī)事務(wù)ACID做對比态贤。無論是數(shù)據(jù)庫領(lǐng)域XA舱呻、Google percolator或Calvin模型,還是微服務(wù)下Saga悠汽、TCC箱吕、可靠消息等方案,都沒有完美解決分布式事務(wù)問題柿冲,它們不過是各自在性能茬高、一致性、可用性等方面做取舍姻采,尋求某些場景偏好下的權(quán)衡雅采。
其實(shí)由于網(wǎng)絡(luò)的不確定性爵憎,分布式下很多問題都是難題慨亲,最好的方案是避免分布式事務(wù):)
最后回到主題,Seata解決了分布式事務(wù)難題了嗎宝鼓?看你最在意哪方面了刑棵?如果你希望業(yè)務(wù)盡量少感知,DB操作簡單愚铡,那它會給你帶來驚喜蛉签;但如果你更看重響應(yīng)時間,DB寫操作較多沥寥,調(diào)用鏈條較長碍舍,那它可能會讓失望。最后希望Seata開源項(xiàng)目越做越好邑雅!