新聞中心
在微服務架構中,單個服務內(nèi)的事務通常使用ACID事務來提供數(shù)據(jù)一致性。

介紹
本文描述了在微服務系統(tǒng)中管理分布式和長時間運行的事務的架構和概念框架。作者發(fā)表此文旨在與開發(fā)社區(qū)分享經(jīng)驗,表達對事件驅動架構的熱情,并促進對復雜事件處理分布式系統(tǒng)的討論興趣。
概述
微服務在其真正的場景中是一個分布式系統(tǒng)。一個事務被分發(fā)到多個服務,這些服務被順序或并行調(diào)用以完成整個事務。在微服務架構中,單個服務中的事務使用ACID事務來提供數(shù)據(jù)一致性。然而,面臨挑戰(zhàn)在于處理跨多個服務的事務,在某些情況下需要很長時間才能完成。在這種情況下,應用程序必須使用復雜的機制來管理事務。而ACID是指數(shù)據(jù)庫管理系統(tǒng)(DBMS)在寫入或更新資料的過程中,為保證事務是正確可靠的,所必須具備的四個特性:原子性、一致性、隔離性、持久性。
設想
考慮一個使用微服務架構實現(xiàn)的航空公司航班座位預訂簡單的場景。在這個場景中,一個微服務來鎖定預訂座位,另一個微服務接受付款,還有一個微服務在付款之后解鎖并分配席位,每個微服務都實現(xiàn)一個本地事務。旅客要成功完成航班預訂流程,必須完成所有三個步驟。如果任何一個步驟失敗,則必須回滾之前完成的所有步驟。由于整體事務的邊界跨越多個服務和數(shù)據(jù)庫,因此被認為是一個分布式事務。
考慮通過微服務方法實現(xiàn)另一個訂單實現(xiàn)場景。工作流事務從訂單服務開始,首先創(chuàng)建訂單,隨后采用另一個服務進行付款,接下來為交易創(chuàng)建發(fā)票,然后進行發(fā)貨,最后交付訂單并完成工作流,并循環(huán)執(zhí)行每個本地事務。這里的訂單處理本質(zhì)上是分布式的,完成工作流程可能需要幾天到幾周的時間。這樣的事務可以稱為長時間運行的事務,因為不能使用傳統(tǒng)的ACID事務語義一次性執(zhí)行所有步驟。
挑戰(zhàn)
隨著微服務架構的出現(xiàn),分布式事務管理存在兩個關鍵問題:
- 原子性:原子性意味著事務中的所有步驟都必須獲得成功,或者如果一個步驟失敗,則應該回滾之前完成的所有步驟。但是在微服務架構中,一個事務可以由不同微服務處理的多個本地事務組成。因此,如果其中一個本地事務失敗,那么如何回滾之前成功完成的事務?
- 隔離性:事務隔離指定對事務中的語句可見的數(shù)據(jù)量,特別是當多個服務調(diào)用同時訪問同一數(shù)據(jù)源時。如果來自任何一個微服務的對象持久地保存在數(shù)據(jù)庫中,而另一個請求同時讀取同一個對象,那么該服務應該提交舊數(shù)據(jù)還是新數(shù)據(jù)?
為了解決這些問題并提供有效的事務管理能力,可以采取兩種方法:一是兩階段提交(2PC) ;二是Saga。
(1)兩階段提交(2PC)
保持跨多個服務的數(shù)據(jù)一致性的傳統(tǒng)方法是使用分布式事務,其事實標準是兩階段提交(2PC)。兩階段提交(2PC) 確保事務中的所有參與者或者提交或者回滾。它分為兩個階段工作;階段1稱為準備階段,控制節(jié)點詢問所有參與節(jié)點是否準備好提交;階段2稱為提交階段,如果所有節(jié)點都回答是肯定的,則控制節(jié)點要求它們提交,否則回滾。
盡管兩階段提交(2PC)可以幫助在分布式系統(tǒng)中提供事務管理,但它也會成為單點故障,因為事務的責任落在了協(xié)調(diào)器上,并且這種協(xié)調(diào)器的典型實現(xiàn)本質(zhì)上是同步的,這會導致減少未來的吞吐量。因此,兩階段提交(2PC)還存在以下不足:
- MongoDB和Cassandra等現(xiàn)代NoSQL數(shù)據(jù)庫不提供支持。
- Apache Kafka等現(xiàn)代消息代理不提供支持。
- 同步IPC降低了可用性。
- 所有參與者都必須在場。
(2)Saga
為了解決在微服務架構中維護數(shù)據(jù)一致性這一更復雜的問題,應用程序必須使用基于松耦合異步服務概念的不同機制。這就是Saga發(fā)揮重要作用的地方。
Saga是一種架構模式,它提供了一種優(yōu)雅的方法來實現(xiàn)跨多個服務的事務,在本質(zhì)上是異步和反應式的。因此,Saga可以定義為事件驅動的本地事務序列,其中每個本地事務更新數(shù)據(jù)庫,并發(fā)布命令或事件以觸發(fā)Saga中的下一個本地事務。如果本地事務因為違反業(yè)務規(guī)則而失敗,那么Saga將執(zhí)行一系列補償事務,這些補償事務將撤消先前本地事務所做的更改。
Saga實現(xiàn)確保執(zhí)行所有事務或撤消所有更改,從而提供原子性保證。將Saga設計為狀態(tài)機模型將提供處理隔離的對策。
Saga模式如何提供幫助
使用微服務架構,單個業(yè)務流程將多個微服務聚合在一起以提供整體解決方案。使用微服務架構實現(xiàn)ACID(原子性、一致性、隔離性、持久性)事務非常困難,并且在某些情況下是不可能的。
例如,在以上提到的航班座位預訂場景中,具有預訂座位功能的微服務無法實現(xiàn)支付數(shù)據(jù)庫的鎖定,因為它在大多數(shù)情況下可能是外部服務。但是仍然需要某種形式的事務管理,這種事務被稱為BASE事務:基本可用、軟狀態(tài)和最終一致。
必須采取補償措施來恢復作為事務一部分發(fā)生的任何事情。以下是Saga如何為航班預訂座位場景圖:
補償事務
當Saga的一個步驟由于違反業(yè)務規(guī)則而失敗時,Saga必須通過執(zhí)行補償事務撤消先前步驟所做的更新。假設Saga的第(n+1)個交易失敗,則必須撤銷之前n個事務的影響。
從概念上來說,每個步驟Ti都有一個相應的補償事務Ci,它可以消除Ti的影響。為了消除前n個步驟的影響,Saga必須以相反的順序執(zhí)行每個Ci。如圖所示,其步驟順序為T1…Tn、Cn…C1。
在這一示例中,Tn+1步驟失敗,這需要撤消T1…Tn步驟。Saga以與遠期事務相反的順序執(zhí)行補償事務:Cn…C1。Cis測序的機制與Tis測序沒有任何區(qū)別。Ci的完成必須觸發(fā)Ci-1的執(zhí)行。
Pivot事務和Retryable事務
下表顯示了Saga在航班座位預訂中每個步驟的補償事務,其三個步驟被稱為補償事務,因為它們之后是可能失敗的步驟。在此需要注意的是,并非所有步驟都需要補償事務。
Saga模式中還有另外兩種事務類型;一個是Pivot事務,就像Saga中的一個成功/失敗點。如果Pivot事務提交,則Saga將一直運行到結束。另一個是Retryable事務,跟隨Pivot事務并保證事務成功。
Saga保證
Saga保證以下兩種結果之一: Saga中的所有請求或者都成功完成,或者執(zhí)行一部分請求及其補償請求。而請求和補償請求都需要遵循一定的原則:
- 單個事務可以中止,并且必須是冪等的。
- 補償事務必須是冪等的、可交換的,并且不能中止(必須無限期重試或在必要時通過人工干預解決)。
Saga協(xié)調(diào)策略
Saga執(zhí)行協(xié)調(diào)器(SEC)是實現(xiàn)成功的Saga流程的核心組件。Saga協(xié)調(diào)可在以下方面實施:
- 編排(choreography)——在Saga參與者之間分配決策和排序。換句話說,參與者在沒有集中控制點的情況下交換事件,每個本地事務都會發(fā)布觸發(fā)其他服務中的本地事務的域事件。
盡管Saga編排是簡單且可靠的基于事件的通信,但它只能處理簡單用例,并且存在一些限制,這使其無法成為管理分布式事務的理想選擇。而基于編排的Saga難以理解,經(jīng)常會產(chǎn)生循環(huán)依賴,并且Saga參與者之間存在緊密耦合的風險。
- 協(xié)調(diào)(orchestration)——將Saga的協(xié)調(diào)邏輯集中在Saga協(xié)調(diào)器類中。Saga協(xié)調(diào)器向Saga 參與者發(fā)送命令并對事件的結果采取行動。協(xié)調(diào)器執(zhí)行Saga請求,存儲和解釋每個任務的狀態(tài),并通過補償事務處理故障恢復?;趨f(xié)調(diào)器的Saga更適合復雜的事件處理,并使它們成為管理分布式事務的一種理想選擇。
Saga協(xié)調(diào)器(Saga Orchestrator)
正如Saga“協(xié)調(diào)”模式所暗示的那樣,有一個單獨的協(xié)調(diào)器組件負責管理整個流程工作流。在使用編制時可以定義一個協(xié)調(diào)器類,它的唯一職責是告訴Saga參與者要做什么。Saga協(xié)調(diào)器使用命令/異步回復式交互與參與者進行通信。為了執(zhí)行Saga步驟,它會向參與者發(fā)送命令消息,告訴它要執(zhí)行什么操作。在Saga參與者執(zhí)行操作后,它會向協(xié)調(diào)器發(fā)送回復消息。然后協(xié)調(diào)器處理這一消息并確定下一步要執(zhí)行的Saga步驟。
上圖顯示了航班座位預訂采用Saga的基于協(xié)調(diào)器的過程。Saga由Saga協(xié)調(diào)器組件編排,該組件使用異步請求/響應調(diào)用Saga參與者。Saga協(xié)調(diào)器跟蹤進程并通過命令組件向Saga參與者發(fā)送命令操作,例如座位鎖定服務(Seat Blocking Service)和付款服務(Payment Service),并通過事件處理器從其回復通道讀取回復消息,然后確定下一個步驟,采用Saga協(xié)調(diào)器預訂航班座位的步驟如下:
(1)FrontEnd UI向Saga協(xié)調(diào)器發(fā)送座位預訂請求。
(2)Saga協(xié)調(diào)器啟動一個新的工作流并向座位鎖定服務(Seat Blocking Service)發(fā)送一個座位鎖定命令(Seat Blocking Command)。
(3)座位鎖定服務(Seat Blocking Service)處理命令并回復一個座位鎖定事件(Seat Blocked Event)。
(4)Saga協(xié)調(diào)器觸發(fā)工作流中的下一個動作,并向付款服務(Payment Service)發(fā)送付款請求命令(Payment Request Command)。
(5)付款服務(Payment Service)回復付款成功事件(Payment Success Event)。
(6)Saga協(xié)調(diào)器然后向座位分配服務(Seat Allocation Service)發(fā)送一個座位分配命令(Seat Allocation Command)。
(7)座位分配服務(Seat Allocation Service)回復一個座位分配事件(Seat Allocated Event)。
(8)Saga協(xié)調(diào)器結束事務并完成工作流。
但是,如果座位鎖定服務、付款服務或座位分配服務中任何一個步驟失敗,Saga航班預訂的場景可能會失敗。為了有效地管理工作流并處理故障,建議將Saga建模為狀態(tài)機,因為它描述了所有可能的場景,并讓協(xié)調(diào)器確定需要執(zhí)行的操作。
作為狀態(tài)機的Saga
將Saga協(xié)調(diào)器建模為狀態(tài)機是一種有效的方式,不僅可以管理分布式事務,還可以支持長時間運行的事務。狀態(tài)機由一組狀態(tài)和一組由事件觸發(fā)的狀態(tài)之間的轉換組成。每個轉換都可以有一個動作,對于Saga來說,它是一個Saga參與者的調(diào)用。
狀態(tài)之間的轉換由Saga參與者執(zhí)行的本地事務的完成觸發(fā)。當前狀態(tài)和本地事務的特定結果決定了狀態(tài)轉換以及要執(zhí)行的操作。因此,使用狀態(tài)機模型可以更輕松地設計、實現(xiàn)和測試Sagas。
上圖突出顯示了Saga航班預訂的狀態(tài)機模型。該狀態(tài)機由許多狀態(tài)和轉換組成,其中包括以下內(nèi)容:
- 開放訂單(Order Open)——初始狀態(tài)。Saga在工作流開始時設置這一狀態(tài)。
- 鎖定座位(Blocking Seat)——當處于這種狀態(tài)時,Saga正在等待座位鎖定服務(Seat Blocking Service)來阻止預訂座位。
- 授權付款(Authorizing Payment)——Saga正在等待來自付款服務(Payment Service)的支付授權命令的回復。
- 分配座位(Allocating Seat)——在支付成功后等待座位分配服務(Seat Allocation Service)分配座位。
- 反向付款(Reverse Payment)——如果座位分配失敗,Saga將發(fā)送付款退款請求。
- 解鎖座位(Unblock Seat)——如果支付授權失敗,Saga將發(fā)送一個失敗事件來解除對座位的封定。
- 訂單完成(Order Completed)——表示Saga成功完成的最終狀態(tài)。
- 訂單被拒絕(Order Rejected)——表示訂單被其中一位參與者拒絕的最終狀態(tài)。
最后,Saga工作流可以重新設計為Saga狀態(tài)機。Saga協(xié)調(diào)器鏈接到一個狀態(tài)機,它負責通過狀態(tài)管理器API管理事務狀態(tài)。除此之外,它還負責將事務狀態(tài)存儲在持久數(shù)據(jù)存儲設備中,以確保發(fā)生系統(tǒng)故障時的恢復。
因此,Saga狀態(tài)機有責任或者完成所有事務,或者使系統(tǒng)處于已知狀態(tài),以便它可以確定可能執(zhí)行下一個動作狀態(tài)或補償活動的順序,無論發(fā)生的事務是分布的還是長期的。
好處和潛在用例
- 更簡單的依賴關系——Saga協(xié)調(diào)器調(diào)用Saga參與者,但參與者不調(diào)用協(xié)調(diào)器。因此,協(xié)調(diào)器依賴于參與者,反之則不然,因此不存在循環(huán)依賴關系。
- 減少耦合——每個服務都實現(xiàn)了一個由協(xié)調(diào)器調(diào)用的API,因此它不需要知道Saga參與者發(fā)布的事件。
- 關注點分離——Saga協(xié)調(diào)邏輯在Saga協(xié)調(diào)器中實現(xiàn)本地化。域對象更簡單,并且不知道它們參與的Saga。
- 數(shù)據(jù)一致性——跨多個微服務保持數(shù)??據(jù)一致性,無需緊密耦合。
- 開發(fā)人員體驗——設計允許開發(fā)人員只關注Saga參與者的業(yè)務邏輯,并簡化Saga協(xié)調(diào)器上有狀態(tài)工作流的實現(xiàn)。
可以執(zhí)行此類實現(xiàn)的幾個潛在用例:
(1)訂單管理系統(tǒng)
- 電子商務
- 送餐
- 機票預訂
- 酒店/出租車預訂
(2)結算交易。
指南和建議
如果組織正在設計和構建編排器驅動的Saga以支持分布式和長時間運行的事務,則建議遵循以下準則:
- 協(xié)調(diào)器應該只負責管理事務和狀態(tài),此處不應添加任何業(yè)務邏輯。而業(yè)務邏輯應該在各個服務參與者中定義。
- 進出協(xié)調(diào)器的所有事件和命令都應承載事務數(shù)據(jù),而不是引用數(shù)據(jù)。
- 使用異步樣式消息在服務之間進行通信。
- 如果使用消息代理(如Kafka),則實施冪等性和狀態(tài)檢查以提高彈性。
- 適用于在CQRS和Event Sourcing架構中設計命令端(寫入模型)。
本文題目:如何將Saga建模為狀態(tài)機
分享路徑:http://fisionsoft.com.cn/article/djhhseo.html


咨詢
建站咨詢
