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