參考原文:兩天,我把分布式事務(wù)搞完了
2PC和3PC的區(qū)別
- 3PC在2PC基礎(chǔ)上犁享,引入了協(xié)調(diào)者超時和參與者超時機(jī)制文兢。
協(xié)調(diào)者超時:當(dāng)協(xié)調(diào)者接收不到參與者的反饋時即協(xié)調(diào)者超時娱两,協(xié)調(diào)者會默認(rèn)回滾所有參與者。屬于悲觀型
參與者超時:當(dāng)協(xié)調(diào)者掛了后邑茄,參與者一直收不到協(xié)調(diào)者的指令蛋叼。那么參與者會默認(rèn)提交事務(wù),因為當(dāng)前參與者有理由相信所有參與者是同意3PC的第一個階段canCommit的部蛇,所以它大概率是可以提交的摊唇。這個參與者超時機(jī)制,可以解決當(dāng)協(xié)調(diào)者掛了之后涯鲁,2PC協(xié)議的所有參與者一直阻塞的問題巷查,以及順便解決了2PC的協(xié)調(diào)者單點問題。
總結(jié):
- 3PC只是針對2PC的特殊情況做了優(yōu)化抹腿,比如:協(xié)調(diào)者掛了后岛请,參與者沒法推進(jìn)下去的問題。但是3PC并沒有從根本上解決2PC的性能問題警绩,相反由于新增一次網(wǎng)絡(luò)交互崇败,它的性能更差了。所以,目前并沒有3PC的實現(xiàn)后室。XA實現(xiàn)的是2PC缩膝。
Seata的AT、TCC岸霹、SAGA模式之間的區(qū)別
AT模式
Seata框架的默認(rèn)模式疾层,屬于兩階段提交。但是呢松申,它在第一個階段就直接提交了事務(wù)云芦,不讓數(shù)據(jù)庫占用事務(wù)資源。直接提交了贸桶,萬一其他分支事務(wù)有一個或多個執(zhí)行失敗舅逸,想回滾咋辦呢?這個就是AT模式牛逼的地方皇筛,它會創(chuàng)建undo_log表琉历,在執(zhí)行事務(wù)提交的sql時,seata框架會默認(rèn)改造sql水醋,得到執(zhí)行前數(shù)據(jù)的快照旗笔,然后執(zhí)行后再次得到數(shù)據(jù)快照。用這兩個快照拄踪,構(gòu)造一個undo log保存在undo_log表蝇恶。這個表,一定是要和執(zhí)行事務(wù)的表在一個數(shù)據(jù)庫惶桐,屬于一個本地事務(wù)撮弧,保證它們的原子性。
回滾的數(shù)據(jù)(叫補(bǔ)償更合適)已經(jīng)保存好了姚糊,就算當(dāng)前事務(wù)提交了贿衍,我也能明明白白的回滾數(shù)據(jù)。但是還有個問題救恨,當(dāng)前分支事務(wù)提交了贸辈,但是其他分支事務(wù)還在執(zhí)行,相當(dāng)于整個分布式事務(wù)還在進(jìn)行中肠槽。那么又來一個分布式事務(wù)擎淤,之前執(zhí)行完成的分支事務(wù)是不是就可以為這個分布式事務(wù)服務(wù)了,但是前面的分布式事務(wù)還在執(zhí)行中秸仙,它還不知道最終執(zhí)行是commit還是rollback呢揉燃。這個時候,那個提交的分支事務(wù)就不能為其他任何分布式事務(wù)服務(wù)筋栋,所以還需要一個全局lock表,用來阻塞并發(fā)的分布式事務(wù)正驻。把分布式事務(wù)從并發(fā)變成串行執(zhí)行弊攘。注意:這個串行可不是表級別抢腐,而是行級別,lock表只是鎖住行襟交。如果其他分布式事務(wù)想要操作該表的其他行數(shù)據(jù)迈倍,能夠拿到全局鎖,可以并發(fā)執(zhí)行捣域。
TCC模式
TCC 分為指代 Try啼染、Confirm、Cancel焕梅,是一種業(yè)務(wù)層面或者是應(yīng)用層的兩階段提交迹鹅。所以,在Try階段數(shù)據(jù)庫層面的事務(wù)就已經(jīng)提交了贞言,所以每個階段都無需占用數(shù)據(jù)庫資源斜棚。
Try階段需要引入臨時狀態(tài),比如:凍結(jié)狀態(tài)该窗,預(yù)添加等等字段弟蚀,用來保存try階段執(zhí)行的結(jié)果。這個中間態(tài)的引入酗失,對業(yè)務(wù)的數(shù)據(jù)庫設(shè)計有非常大的侵入义钉。同時,每個服務(wù)都需要改造成Try规肴、Confirm捶闸、Cancel三個階段的服務(wù),對代碼侵入也很大奏纪。
比如有一個扣款服務(wù)鉴嗤,我需要寫 Try 方法,用來凍結(jié)扣款資金序调,還需要一個 Confirm 方法來執(zhí)行真正的扣款醉锅,最后還需要提供 Cancel 來進(jìn)行凍結(jié)操作的回滾,對應(yīng)的一個事務(wù)的所有服務(wù)都需要提供這三個方法发绢。
TCC 的注意點
冪等問題硬耍,因為網(wǎng)絡(luò)調(diào)用無法保證請求一定能到達(dá),所以都會有重調(diào)機(jī)制边酒,因此對于 Try经柴、Confirm、Cancel 三個方法都需要冪等實現(xiàn)墩朦,避免重復(fù)執(zhí)行產(chǎn)生錯誤坯认。
空回滾問題,指的是 Try 方法由于網(wǎng)絡(luò)問題沒收到超時了,此時事務(wù)管理器就會發(fā)出 Cancel 命令牛哺,那么需要支持 Cancel 在未執(zhí)行 Try 的情況下能正常的 Cancel陋气。
懸掛問題,這個問題也是指 Try 方法由于網(wǎng)絡(luò)阻塞超時觸發(fā)了事務(wù)管理器發(fā)出了 Cancel 命令引润,但是執(zhí)行了 Cancel 命令之后 Try 請求到了巩趁,你說氣不氣。
這都 Cancel 了你來個 Try淳附,對于事務(wù)管理器來說這時候事務(wù)已經(jīng)是結(jié)束了的议慰,這凍結(jié)操作就被“懸掛”了,所以空回滾之后還得記錄一下奴曙,防止 Try 的再調(diào)用别凹。
Saga 模式
參考原文:Saga模式
這個 Saga 是 Seata 提供的長事務(wù)解決方案,適用于業(yè)務(wù)流程多且長的情況下缆毁,這種情況如果要實現(xiàn)一般的 TCC 啥的可能得嵌套多個事務(wù)了番川。
Saga的組成
每個Saga由一系列sub-transaction Ti 組成
每個Ti 都有對應(yīng)的補(bǔ)償動作Ci,補(bǔ)償動作用于撤銷Ti造成的結(jié)果
可以看到脊框,和TCC相比颁督,Saga沒有“預(yù)留”動作,它的Ti就是直接提交到庫浇雹。
Saga的執(zhí)行順序有兩種:
執(zhí)行成功:T1, T2, T3, ..., Tn
執(zhí)行失敵劣:T1, T2, ..., Tj, Cj,..., C2, C1,其中0 < j < n
Saga定義了兩種恢復(fù)策略:
- backward recovery昭灵,向后恢復(fù)吠裆,補(bǔ)償所有已完成的事務(wù),如果任一子事務(wù)失敗烂完。即上面提到的第二種執(zhí)行順序试疙,其中j是發(fā)生錯誤的sub-transaction,這種做法的效果是撤銷掉之前所有成功的sub-transation抠蚣,使得整個Saga的執(zhí)行結(jié)果撤銷祝旷。
- forward recovery,向前恢復(fù)嘶窄,重試失敗的事務(wù)怀跛,假設(shè)每個子事務(wù)最終都會成功。適用于必須要成功的場景柄冲,執(zhí)行順序是類似于這樣的:T1, T2, ..., Tj(失敗), Tj(重試),..., Tn吻谋,其中j是發(fā)生錯誤的sub-transaction。該情況下不需要Ci现横。
顯然漓拾,向前恢復(fù)沒有必要提供補(bǔ)償事務(wù)阁最,如果你的業(yè)務(wù)中,子事務(wù)(最終)總會成功骇两,或補(bǔ)償事務(wù)難以定義或不可能闽撤,向前恢復(fù)更符合你的需求。
理論上補(bǔ)償事務(wù)永不失敗脯颜,然而,在分布式世界中贩据,服務(wù)器可能會宕機(jī)栋操,網(wǎng)絡(luò)可能會失敗,甚至數(shù)據(jù)中心也可能會停電饱亮。在這種情況下我們能做些什么矾芙? 最后的手段是提供回退措施,比如人工干預(yù)近上。
對于ACID的保證:
Saga對于ACID的保證和TCC一樣:
- 原子性(Atomicity):不支持
- 一致性(Consistency)剔宪,在某個時間點,會出現(xiàn)A庫和B庫的數(shù)據(jù)違反一致性要求的情況壹无,但是最終是一致的葱绒。
- 隔離性(Isolation):不支持,A事務(wù)能夠讀到B事務(wù)部分提交的結(jié)果斗锭。
持久性(Durability):和本地事務(wù)一樣地淀,只要commit則數(shù)據(jù)被持久。
Saga不提供ACID保證岖是,因為原子性和隔離性不能得到滿足帮毁。原論文描述如下:
full atomicity is not provided. That is, sagas may view the partial results of other sagas
通過saga log,saga可以保證一致性和持久性豺撑。
和TCC對比
Saga相比TCC的缺點是缺少預(yù)留動作烈疚,導(dǎo)致補(bǔ)償動作的實現(xiàn)比較麻煩:Ti就是commit,比如一個業(yè)務(wù)是發(fā)送郵件聪轿,在TCC模式下爷肝,先保存草稿(Try)再發(fā)送(Confirm),撤銷的話直接刪除草稿(Cancel)就行了屹电。而Saga則就直接發(fā)送郵件了(Ti)阶剑,如果要撤銷則得再發(fā)送一份郵件說明撤銷(Ci),實現(xiàn)起來有一些麻煩危号。
如果把上面的發(fā)郵件的例子換成:A服務(wù)在完成Ti后立即發(fā)送Event到ESB(企業(yè)服務(wù)總線牧愁,可以認(rèn)為是一個消息中間件),下游服務(wù)監(jiān)聽到這個Event做自己的一些工作然后再發(fā)送Event到ESB外莲,如果A服務(wù)執(zhí)行補(bǔ)償動作Ci猪半,那么整個補(bǔ)償動作的層級就很深兔朦。
不過沒有預(yù)留動作也可以認(rèn)為是優(yōu)點:
- 有些業(yè)務(wù)很簡單,套用TCC需要修改原來的業(yè)務(wù)邏輯磨确,而Saga只需要添加一個補(bǔ)償動作就行了沽甥。
- TCC最少通信次數(shù)為2n,而Saga為n(n=sub-transaction的數(shù)量)乏奥。
- 有些第三方服務(wù)沒有Try接口摆舟,TCC模式實現(xiàn)起來就比較tricky了,而Saga則很簡單邓了。
- 沒有預(yù)留動作就意味著不必?fù)?dān)心資源釋放的問題恨诱,異常處理起來也更簡單(請對比Saga的恢復(fù)策略和TCC的異常處理)。