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