一篇搞定!10分鐘說透Saga分布式事務
本文轉載自微信公眾號「石杉的架構筆記」,作者崔皓。轉載本文請聯(lián)系石杉的架構筆記公眾號。
開篇
隨著微服務架構的興起,越來越多的公司會在實際場景中遇到分布式事務的問題。特別是在金融應用場景,幾個跨進程的應用共同完成一個任務,就更離不開分布式事務的參與。而對于分布式事務而言,2PC、TCC也是經常被提到了,不過在面對長業(yè)務流程,并且很難進行TCC改造的場景,會選擇使用Saga分布式事務。今天會給大家介紹Saga實現(xiàn)分布式事務的內容:
- Saga的分布式解決方案
- Saga處理事務一致性
- Saga分布式事務協(xié)調
- Saga的分布式解決方案
隨著互聯(lián)網(wǎng)的快速發(fā)展,原來的單體應用已經很難支撐大流量高并發(fā)的請求了,因此軟件系統(tǒng)由原來的單體應用逐漸向分布式過度,如圖1所示,左邊的Web App 包含了UI和服務的模塊,在轉變以后會對應右邊的微服務架構,服務之間存在關聯(lián)地相互調用。
圖1 單體到分布式的系統(tǒng)架構過渡
在進行分布式部署之后,會存在多個服務共同完成一個事務操作,并且這些服務彼此都存在于不同的服務器或者網(wǎng)絡環(huán)境,服務之間需要通過網(wǎng)絡遠程協(xié)作完成事務稱之為分布式事務。例如:銀行轉賬業(yè)務、下單扣件庫存等。
在分布式事務場景下,如果對數(shù)據(jù)有強一致性要求,會在業(yè)務層上才去“兩階段提交”(2PC)的方案。
如果保證最終一致性的話可以采取TCC (Try Confirm Cancel)模式。雖然TCC保證最終一致性的模式被業(yè)內廣泛使用,但是對于某些分布式事務場景,流程多、流程長、還可能要調用其它公司的服務。特別是對于不可控的服務(其他公司的服務),這些服務無法遵循 TCC 開發(fā)模式,導致TCC模式的開發(fā)成本增高。體現(xiàn)在具體場景中,以金融核心的業(yè)務為代表(渠道層、產品層、集成層),其特點是:流程多、流程長、調用不可控服務。同時也是應為流程長,事務邊界太長,加鎖時間長,使用TCC模式會影響并發(fā)性能。
鑒于此類業(yè)務場景的分布式事務處理,提出了Saga分布式處理模式。Saga是一種“長事務的解決方案”,更適合于“業(yè)務流程長、業(yè)務流程多”的場景。特別是針對參與事務的服務是遺留系統(tǒng)服務,此類服務無法提供TCC模式下的三個接口,就可以采用Saga模式。
其適用于的業(yè)務業(yè)務場景有,金融機構對接系統(tǒng)(需要對接外部系統(tǒng))、渠道整合(流程長)、分布式架構服務等。其優(yōu)勢是一階段提交本地事務,無鎖,高性能;參與者可異步執(zhí)行,高吞吐;補償服務易于實現(xiàn),因為一個更新操作的反向操作是比較容易理解的;當然其也存在缺點,就是不保證隔離性。
Saga處理事務一致性
1987年普林斯頓大學的Hector Garcia-Molina和Kenneth Salem發(fā)表了一篇Paper Sagas,講述的是如何處理long lived transaction(長活事務)。Saga是一個長活事務可被分解成可以交錯運行的子事務集合。其中每個子事務都是一個保持數(shù)據(jù)庫一致性的真實事務。
在這位老兄的論文中提到,每個Saga由一系列sub-transaction Ti組成。每個Ti都有對應的補償動作Ci,補償動作用于撤銷Ti造成的結果。這里可以理解為,針對每一個分布式事務的每個執(zhí)行操作或者是步驟都是一個 Ti,例如扣減庫存是T1、創(chuàng)建訂單是T2、支付服務是T3。那么針對每個Ti都對應一個補償動作Ci,例如回復庫存C1、訂單回滾C2、支付回滾C3。
Saga事務有兩種恢復策略:
向前恢復(forward recovery),也就是“勇往直前”。
對于執(zhí)行不通過的事務,會嘗試重試事務,這里有一個假設就是每個子事務最終都會成功。這種方式適用于必須要成功的場景,如圖2 所示,上面的圖例,子事務按照從左到右的順序執(zhí)行,T1執(zhí)行完畢以后T2 執(zhí)行,然后是T3、T4、T5。
圖2 Saga事務執(zhí)行的策略
事務恢復的順序也是按照:T1、T2、T3、T4、T5的方向進行,如果在執(zhí)行T1的時候失敗了就重試T1,以此類推在哪個子事務執(zhí)行時失敗了就執(zhí)行哪個事務。因此叫做“勇往直前”。
向后恢復(backward recovery),在執(zhí)行事務失敗時,補償所有已完成的事務,是“一退到底”的方式。如圖2所示,下面的圖例,子事務依舊從左往右執(zhí)行,在執(zhí)行到事務T3的時候,該事務執(zhí)行失敗了,于是按照紅線的方向開始執(zhí)行補償事務,先執(zhí)行C3、然后是C2和C1,直到T0、T1、T2的補償事務C1、C2、C3都執(zhí)行完畢。也就是回滾整個Saga的執(zhí)行結果。
Saga分布式事務協(xié)調
上面介紹了Saga的概念和事務恢復方式,每個事務存在多個子事務,每個子事務都有一個補償事務,其在事務回滾的時候使用。由于子事務對應的操作在分布式的系統(tǒng)架構中會部署在不同的服務中,這些子事務為了完成共同的事務需要進行協(xié)同。
實際上在啟動一個Saga事務時,協(xié)調邏輯會告訴第一個Saga參與者,也就是子事務,去執(zhí)行本地事務。事務完成之后Saga的會按照執(zhí)行順序調用Saga的下一個參與的子事務。這個過程會一直持續(xù)到Saga事務執(zhí)行完畢。
如果在執(zhí)行子事務的過程中遇到子事務對應的本地事務失敗,則Saga會按照相反的順序執(zhí)行補償事務。通常來說我們把這種Saga執(zhí)行事務的順序稱為個Saga的協(xié)調邏輯。這種協(xié)調邏輯有兩種模式,編排(Choreography)和控制(Orchestration)分別如下:
編排(Choreography):參與者(子事務)之間的調用、分配、決策和排序,通過交換事件進行進行。是一種去中心化的模式,參與者之間通過消息機制進行溝通,通過監(jiān)聽器的方式監(jiān)聽其他參與者發(fā)出的消息,從而執(zhí)行后續(xù)的邏輯處理。由于沒有中間協(xié)調點,靠參與靠自己進行相互協(xié)調。
控制(Orchestration):Saga提供一個控制類,其方便參與者之前的協(xié)調工作。事務執(zhí)行的命令從控制類發(fā)起,按照邏輯順序請求Saga的參與者,從參與者那里接受到反饋以后,控制類在發(fā)起向其他參與者的調用。所有Saga的參與者都圍繞這個控制類進行溝通和協(xié)調工作。
下面通過一個例子來介紹這兩種協(xié)調模式,假設有一個下單的業(yè)務,從訂單服務的創(chuàng)建訂單操作發(fā)起,會依次調用支付服務中的支付訂單,庫存服務中的扣減庫存以及發(fā)貨服務中的發(fā)貨操作,最終如果所有參與者(服務)中的操作(子事務)完成的話,整個下單事務就算完成。
編排(Choreography),由于沒有中心的控制類參與參與者操作之間的協(xié)調工作,因此通過消息發(fā)送的方式進行協(xié)調。
如圖3所示:
圖3 編排模式-事務執(zhí)行成功
1. “訂單服務”中執(zhí)行“創(chuàng)建訂單”操作,此時會發(fā)送一個“創(chuàng)建訂單消息”到隊列中。
2. “支付服務”監(jiān)聽到隊列中的這個訂單消息,調用“支付訂單”的操作,同時也發(fā)送“只服務消息”到隊列中。
3. “庫存服務”在監(jiān)聽到“支付消息”之后會進行“扣減庫存”的處理,并且發(fā)送“扣減庫存消息”等待下一個消費者接受。
4. “發(fā)貨服務”作為整個事務的最后一個子事務,在接到“扣減庫存消息”以后會執(zhí)行發(fā)貨的子事務,完成事務以后會給“訂單服務”發(fā)送“發(fā)貨消息”,訂單服務在接受到消息以后完成整個事務閉環(huán),并且提交。
上面說的是事務執(zhí)行成功的情況,如果事務執(zhí)行失敗那應該如何處理?
如圖4所示:
圖4 編排模式-事務執(zhí)行失敗
1. 假設在執(zhí)行“發(fā)貨”時子事務失敗了,會發(fā)送“發(fā)貨失敗消息”。
2. 庫存服務在接受到“發(fā)貨失敗消息”之后會執(zhí)行“回滾庫存”的操作,該操作將原來扣減的庫存加回去,同時發(fā)送“扣減失敗消息”。
3. “支付服務”在接受到“扣減失敗消息”之后會執(zhí)行“回滾支付”,進行退款的操作,同時發(fā)送“支付失敗消息”。訂單服務在接受到該消息以后將下單事務標記為失敗。
從上面的描述可以看出編排的好處:
簡單:每個子事務進行操作時只用發(fā)布事件消息,其他子事務監(jiān)聽處理。
松耦合:參與者(服務)之間通過訂閱事件進行溝通,組合會更加靈活。
當然也有一些缺點:
理解困難:沒有對業(yè)務流程進行完整的描述,要了解整個事務的執(zhí)行過程需要通過閱讀代碼完成。增加開發(fā)人員理解和維護代碼的難度。
存在服務的循環(huán)依賴:由于通過消息和事件進行溝通,參與者之間會存在循環(huán)依賴的情況。也就是A服務調用B服務,B服務又調用A服務的情況。這也增加了架構設計的復雜度,在設計初期需要認真考慮。
緊耦合風險:每個參與者執(zhí)行的方法都依賴于上一步參與者發(fā)出的消息,但是上一步的參與者的所有消息都需要被訂閱,才能了解參與者的真實狀態(tài),無形中增加了兩個服務的耦合度。
控制(Orchestration),其核心是定義一個控制類,它會告訴參與者(服務)應該執(zhí)行哪些操作(子事務)。 Saga控制類通過命令以及異步回復的方式與參與者進行交互。
如圖5所示:
圖5 控制模式-成功
1. 訂單服務執(zhí)行下單事務時,向Saga協(xié)調器發(fā)送請求命令,Saga協(xié)調器接受到命令以后按照子事務執(zhí)行的順序調用服務中的方法。
2. 最開始執(zhí)行“支付訂單”的操作,調用“支付服務”中的“支付訂單”操作,并且通過虛線的部分返回執(zhí)行結果“支付完成”。
3. 接下來,執(zhí)行“庫存服務”中的“扣減庫存”方法,同樣通過虛線部分返回扣減完成的消息給“請求反饋“模塊。
4. 緊接著就是執(zhí)行“發(fā)貨“命令,調用”發(fā)貨服務“中的”發(fā)貨“方法,并且返回”發(fā)貨完成“的響應。
5. 最后,三個子事務都執(zhí)行完畢以后,返回訂單服務,完成整個分布式事務。
介紹完成成功完成事務之后,再來看看出現(xiàn)異常的情況。
如圖6所示:
圖6 控制模式-失敗
1. 在執(zhí)行“發(fā)貨”命令時發(fā)現(xiàn)“發(fā)貨失敗”,于是“發(fā)貨服務”反饋給Saga協(xié)調器。
2. 此時協(xié)調器調用“庫存服務”中的“回滾庫存”操作,將扣減的庫存恢復。
3. 然后調用“支付服務”中的“回滾支付”完成支付退款的工作。
4. 最后,通知訂單服務事務處理失敗。
需要指出的是控制模式也是基于事件驅動的,與編排模式一樣會發(fā)送消息通知參與者執(zhí)行命令,上面兩個圖中命令的執(zhí)行和調用也是通過消息的方式進行。
控制器設計的優(yōu)點:
避免循環(huán)依賴:在編排模式中存在參與者之間的循環(huán)調用,而中心控制類的方式可以避免這種情況的發(fā)生。
降低復雜性:所有事務交給控制器完成,它負責命令的執(zhí)行和回復的處理,參與者只需要完成自身的任務,不用考慮處理消息的方式,降低參與者接入的復雜性。
容易測試:測試工作集中在集中控制類上,其他服務單獨測試功能即可。
容易擴展:如果事務需要添加新步驟,只需修改控制類,保持事務復雜性保持線性,回滾更容易管理。
當然這種方法也存在缺點:
依賴控制器:控制器中集中太多邏輯的風險。
增加管理難度:這種模式除了管理各個業(yè)務服務以外,還需要額外管理控制類服務,無形中增加了管理的難度和復雜度。而且存在單點風險,一旦控制器出現(xiàn)問題,整個業(yè)務就處于癱瘓中。
總結
這里對Saga進行一個總結,首先Saga是針對分布式長活事務的解決方案,針對事務長、多、復雜的情況,特別是服務由多個公司開發(fā)具有不可控性,可以使用Saga模式進行分布式事務的處理。Saga在處理事務一致性方面采取了向前恢復和向后恢復策略,前者通過不斷重試的方式保證事務完成,而后者通過子事務的補償事務,逐一回滾的方式讓事務標記失敗。在分布式協(xié)調方面,Saga采用了兩種模式:編排和控制。前者讓參與者(服務)之間通過消息進行溝通,根據(jù)事件出發(fā)事務的執(zhí)行流程,是一種去中心化的模式。后者通過中心控制類,處理事務的執(zhí)行和回滾步驟,統(tǒng)一調用服務和接受服務的反饋。