Saga 模式服務(wù)設(shè)計
基于 Saga 模式分布式事務(wù)的多年實踐,本文提供了在 Saga 模式下服務(wù)設(shè)計的一些最佳實踐與經(jīng)驗。
服務(wù)執(zhí)行與補償
Saga 模式是 SEATA 提供的長事務(wù)解決方案。Saga 模式下,分布式事務(wù)內(nèi)存在多個參與者,每一個參與者都是一個沖正補償服務(wù),用戶需要根據(jù)業(yè)務(wù)場景實現(xiàn)其正向操作(原服務(wù))和逆向回滾操作(補償服務(wù))。在事務(wù)執(zhí)行過程中,首先會依次執(zhí)行各參與者的正向操作,如所有正向操作執(zhí)行成功,則事務(wù)提交。如任一正向操作執(zhí)行失敗,則事務(wù)會執(zhí)行之前各參與者的逆向回滾操作,回滾已提交的參與者,直至事務(wù)退回至其初始狀態(tài)。
允許服務(wù)空補償
空補償,指的是原服務(wù)未執(zhí)行,補償服務(wù)已執(zhí)行。大致場景如下:
針對該問題,在服務(wù)設(shè)計時,需要允許空補償,即在沒有找到要補償?shù)臉I(yè)務(wù)主鍵時,返回補償成功,并將原業(yè)務(wù)主鍵記錄下來,標(biāo)記該業(yè)務(wù)流水已補償成功。
服務(wù)防懸掛控制
懸掛,指的是補償服務(wù)比原服務(wù)先執(zhí)行。大致場景如下:
此時,需要檢查當(dāng)前業(yè)務(wù)主鍵是否已經(jīng)在空補償記錄下來的業(yè)務(wù)主鍵中存在,如果存在則要拒絕執(zhí)行該筆服務(wù),以免造成數(shù)據(jù)不一致。
服務(wù)冪等控制
在分布式事務(wù)執(zhí)行過程中,原服務(wù)與補償服務(wù)都需要保證冪等性。由于網(wǎng)絡(luò)連接可能超時,可以設(shè)置重試策略,重試發(fā)生時要通過冪等控制避免業(yè)務(wù)數(shù)據(jù)重復(fù)更新。
判斷服務(wù)狀態(tài)
Saga 發(fā)起方作為服務(wù)方,需要根據(jù)狀態(tài)機的執(zhí)行狀態(tài),返回服務(wù)狀態(tài)給調(diào)用方。可以參考以下代碼示例:
StateMachineInstance inst = stateMachineEngine.startWithBusinessKey("testTransferBySaga",null, businessKey, params);
if(ExecutionStatus.SU.equals(inst.getStatus()) && inst.getCompensationStatus()==null){
//正向狀態(tài)為成功, 補償狀態(tài)為空(沒有觸發(fā)回滾),則交易成功,返回 SU
}else if(ExecutionStatus.SU.equals(inst.getCompensationStatus())){
//補償狀態(tài)為成功,則交易回滾成功,返回失敗 FA
//如果有多級調(diào)用,可以給調(diào)用方返回失敗 FA,這樣上層編排則不再進行補償該服務(wù)
}else if(ExecutionStatus.FA.equals(inst.getStatus()) && inst.getCompensationStatus()==null){
//正向狀態(tài)為失敗, 補償狀態(tài)為空(沒為觸發(fā)回滾),則交易失敗且沒有數(shù)據(jù)不一致,返回FA
}else{
//其它情況說明是正向或補償結(jié)果未知,返回未知UN給調(diào)用方,需要進行重試
}
應(yīng)對隔離性問題
Saga 模式由于一階段已經(jīng)提交本地數(shù)據(jù)庫事務(wù),且沒有進行預(yù)留動作,因此無法保證隔離性。在極端情況下,可能由于臟寫(即當(dāng)本地事務(wù)提交之后、分布式事務(wù)完成之前,該數(shù)據(jù)被補償之前又被其他操作修改過。)無法完成回滾操作。例如,在分布式事務(wù)中,需要先給用戶 A 充值,然后給用戶 B 扣減余額。如果在給用戶 A 充值成功,在事務(wù)提交以前,用戶 B 已經(jīng)把余額消費掉了,如果事務(wù)發(fā)生回滾,此時則沒有辦法進行補償了。
以上是一個極端場景下隔離性缺乏造成的典型問題。在實踐中,一般可以采取以下方法應(yīng)對該問題:
業(yè)務(wù)流程設(shè)計時,始終遵循“寧可長款,不可短款”的原則。長款指的是客戶少了錢機構(gòu)多了錢,以機構(gòu)信譽可以給客戶退款;反之則是短款,少的錢可能追不回來了。所以在業(yè)務(wù)流程設(shè)計上,一定是先扣款。
有些業(yè)務(wù)場景可以允許讓業(yè)務(wù)最終成功,在回滾不了的情況下,可以繼續(xù)重試完成后面的流程。所以狀態(tài)機引擎除了提供“回滾”能力還需要提供“向前”恢復(fù)上下文繼續(xù)執(zhí)行的能力,讓業(yè)務(wù)最終執(zhí)行成功,達到最終一致性的目的。