微服務架構中的 Saga 模式
一、簡介
在典型的基於微服務的架構中,單一業務用例跨越多個微服務,每個服務都有自己的本機資料儲存和本地化交易。當涉及多個事務且微服務數量龐大時,就需要處理跨各種服務的事務。
引入 Saga 模式來處理這些多個事務。它最初由 Hector Garcia Molina 和 Kenneth Salems 於 1987 年提出,被定義為一系列可以相互交錯的事務。
在本教程中,我們將深入探討管理分散式事務的挑戰、基於編排的 Saga 模式如何解決這個問題,以及使用 Spring Boot 3 和 Orkes Conductor(領先的企業級版本)實現 Saga 模式的範例。開源編排平台Conductor OSS (以前稱為 Netflix Conductor)。
2. 管理分散式事務的挑戰
如果分散式事務實施不正確,就會帶來很多挑戰。在分散式事務中,每個微服務都有一個單獨的本機資料庫。這種方法通常稱為「每個服務資料庫」模型。
例如,MySQL 可能因其效能特徵和功能而適合一種微服務,而 PostgreSQL 可能因其優勢和功能而被選用於另一種微服務。在此模型中,每個服務執行其本地事務來完成整個應用程式事務。這整個事務稱為分散式事務。
分散式事務可以透過多種方式處理。兩種傳統方法是 2PC(兩階段提交)和 ACID(原子性、一致性、隔離性、持久性)事務,每種方法都有其挑戰,例如多語言持久性、最終一致性、延遲等。
3.理解Saga模式
Saga 模式是一種用於實現一系列本地事務的架構模式,有助於維護不同微服務之間的資料一致性。
本地事務更新其資料庫並透過發布訊息或事件來觸發下一個事務。如果本地事務失敗,saga 會執行一系列補償事務來回滾先前事務所所做的變更。這確保了即使事務失敗,系統也能保持一致。
為了進一步說明這一點,請考慮一個訂單管理系統,該系統由從下訂單到交付訂單的連續步驟組成:
在此範例中,該過程從用戶從應用程式下訂單開始。然後,流程會經歷幾個步驟:庫存檢查、付款處理、運輸和通知服務。
如果付款失敗,應用程式必須執行補償事務來回滾先前步驟中所做的更改,例如撤銷支付和取消訂單。這確保了 Saga 模式可以處理任何階段的失敗並補償先前的事務。
Saga 模式可以透過兩種不同的方式實現。
編排:在此模式中,各個微服務使用事件、執行活動並將事件傳遞給下一個服務。沒有集中的協調器,使得服務之間的通訊更加困難:
編排:在此模式中,所有微服務都連結到集中協調器,該協調器按預先定義的順序編排服務,從而完成應用程式流程。這有利於可見性、監控和錯誤處理:
4. 為什麼選擇基於編排的Saga模式?
編排模式中的分散方法使得管理和監控服務互動變得更具挑戰性。由於缺乏集中協調和可見性,複雜性增加,使得應用程式更難以維護。
讓我們來看看 Choreography 的主要缺點以及選擇 Orchestration 的優點。
4.1.編排的局限性
在建立分散式應用程式時,基於編排的實作有很多限制:
- 緊密耦合-服務是緊密耦合的,因為它們是直接連接的。應用程式中服務的任何變更都可能影響所有連線的服務,因此在升級服務時需要依賴關係。
- 分散式事實來源-跨各種微服務維護應用程式狀態會使流程流的追蹤變得複雜,並且可能需要額外的系統來整合狀態資訊。這增加了基礎設施並為整個系統帶來了複雜性。
- 難以排除故障 – 當應用程式流程分佈在不同的服務中時,可能需要更長的時間來尋找和修復問題。故障排除需要集中的日誌服務和對程式碼的充分理解。如果一項服務發生故障,可能會導致更嚴重的問題,甚至可能造成大規模的中斷。
- 具有挑戰性的測試環境-由於微服務彼此互連,測試對於開發人員來說變得困難。
- 難以維護-隨著服務的發展,合併新版本涉及重新引入條件邏輯,從而再次形成分散式整體。這使得在不檢查整個程式碼的情況下理解服務流變得更加困難。
4.2.編排的優點
在建立分散式應用程式時,基於編排的實作具有許多優點:
- 分散式系統內的協調事務-不同的微服務處理分散式系統中事務的不同面向。透過基於編排的模式,中央協調器以預先定義的方式管理這些微服務的執行。它主動確保各個本地事務的精確執行,從而保持應用程式的一致性。
- 補償事務-在應用程式中,由於任何錯誤,任何執行點都可能發生故障。 Saga 模式允許在發生故障時執行補償事務。它可以回滾之前完成的事務,確保應用程式保持一致的狀態。
- 非同步處理-每個微服務都可以獨立處理其活動,集中協調器可以管理這些非同步操作的通訊和排序。這在特定步驟可能需要較長時間才能完成或需要並行處理的情況下非常有用。
- 可擴展性——編排模式具有高度可擴展性,這意味著我們可以透過簡單地添加或修改所需的服務來對應用程式進行更改,而不會顯著影響整個應用程式。這在應用程式需要適應不斷變化的需求、允許輕鬆擴展或修改架構的情況下特別有用。
- 增強的可見性和監控功能-利用編排模式提供跨分散式應用程式的集中可見性,從而實現快速識別和解決問題。這提高了生產力,最大限度地減少了停機時間,並最終減少了檢測故障和從故障中恢復的平均時間。
- 更快的上市時間-編排器簡化了現有服務的重新佈線和新流程的創建,促進快速適應。這使得應用程式團隊變得更加敏捷,從而加快新想法和概念的上市時間。此外,編排器通常會管理版本控制,從而減少程式碼中使用大量「if..then..else」語句來建立不同版本的需要。
總而言之,基於編排的 Saga 模式提供了一種在微服務架構中實現協調、一致和可擴展的分散式事務的方法,並具有透過補償事務處理故障的額外好處。這使其成為建立健壯且可擴展的分散式應用程式的強大模式。
5. 使用 Orkes Conductor 實作 Saga 編排模式
現在,讓我們來看看使用 Saga 模式和 Orkes Conductor 的應用程式的實際範例。
考慮一個具有以下服務的訂單管理系統:
- OrderService – 處理初始訂單放置,包括將商品新增至購物車、指定數量以及初始化結帳流程。
- InventoryService – 檢查並確認物品的可用性。
- PaymentService – 安全地管理支付流程,處理各種支付方式。
- ShipmentService – 準備運輸物品,包括包裝、產生運輸標籤和啟動運輸流程。
- NotificationService – 向使用者發送有關訂單更新的通知。
讓我們探索使用 Orkes Conductor 和 Spring Boot 3 複製此流程。
在開始應用程式開發之前,請確保系統符合以下先決條件。
要為我們的應用程式設定 Orkes Conductor,我們可以選擇以下任意方法:
在此範例中,我們將使用 Playground。
以下是使用 Saga 模式建立的送餐應用程式的程式碼片段:
@AllArgsConstructor
@Component
@ComponentScan(basePackages = {"io.orkes"})
public class ConductorWorkers {
@WorkerTask(value = "order_food", threadCount = 3, pollingInterval = 300)
public TaskResult orderFoodTask(OrderRequest orderRequest) {
String orderId = OrderService.createOrder(orderRequest);
TaskResult result = new TaskResult();
Map<String, Object> output = new HashMap<>();
if(orderId != null) {
output.put("orderId", orderId);
result.setOutputData(output);
result.setStatus(TaskResult.Status.COMPLETED);
} else {
output.put("orderId", null);
result.setStatus(TaskResult.Status.FAILED);
}
return result;
}
}
5.1.送餐應用
從 Conductor UI 來看,範例送餐應用程式如下所示:
在遊樂場查看
讓我們看看工作流程是如何進行的:
- 當用戶在食品配送應用程式上下訂單時,該應用程式就會啟動。初始流程作為一系列工作任務實現,包括將食物添加到購物車 (
order_food
)、檢查餐廳的食物供應情況 (check_inventory
)、付款流程 (make_payment
) 和配送流程 (ship_food
)。 - 然後應用程式流程繼續進行fork-join 任務,該任務處理通知服務。它有兩個叉子,一個用於通知送貨員,另一個用於通知用戶。
現在,讓我們運行該應用程式!
5.2.運行應用程式
- 克隆項目。
- 使用產生的存取金鑰更新
application.properties
檔案。為了將此工作執行緒與應用程式伺服器執行個體連接(先前解釋的工作流程),我們需要在 Orkes Conductor 中建立一個應用程式並產生存取金鑰。
conductor.server.url=https://play.orkes.io/api
conductor.security.client.key-id=<key>
conductor.security.client.secret=<secret>
筆記:
- 由於我們使用的是 Playground, conductor.server.url**保持不變**。如果我們在本地設定了 Conductor,請將其替換為 Conductor 伺服器 URL。
- 將
key-id
和secret
替換為產生的金鑰。 - 為了讓工作人員與 Conductor 伺服器連接,我們需要提供存取工作流程和任務的權限(在我們剛剛建立的應用程式中) 。
- 預設情況下,
conductor.worker.all.domain
**設定為 'saga'** 。確保使用不同的名稱進行更新,以避免與 Orkes Playground 中其他人啟動的工作流程和工作人員發生衝突。
讓我們使用以下命令從根專案運行應用程式:
gradle bootRun
應用程式正在運行;下一步是透過從應用程式呼叫triggerRideBookingFlow
API 來建立訂單。
$ curl --location 'http://localhost:8081/triggerFoodDeliveryFlow' \
--header 'Content-Type: application/json' \
--data '{
"customerEmail": "[email protected]",
"customerName": "Tester QA",
"customerContact": "+1(605)123-5674",
"address": "350 East 62nd Street, NY 10065",
"restaurantId": 2,
"foodItems": [
{
"item": "Chicken with Broccoli",
"quantity": 1
},
{
"item": "Veggie Fried Rice",
"quantity": 1
},
{
"item": "Egg Drop Soup",
"quantity": 2
}
],
"additionalNotes": [
"Do not put spice.",
"Send cutlery."
],
"paymentMethod" : {
"type": "Credit Card",
"details": {
"number": "1234 4567 3325 1345",
"cvv": "123",
"expiry": "05/2022"
}
},
"paymentAmount": 45.34,
"deliveryInstructions": "Leave at the door!"
}'
請求發送後,我們將收到一個工作流程 ID,表示我們的送餐應用程式正在運行! 🍕
使用工作流程 ID,我們可以從 Conductor UI 視覺化我們的應用程式。讓我們複製工作流程 ID,然後在 Conductor 控制台上從左側選單導航到「 Executions > Workflow
」 ,然後使用工作流程 ID 搜尋執行。
範例執行如下圖所示:
讓我們看看如果其中一項服務失敗,應用程式流程會發生什麼情況。
5.3.補償流量
以下是食品配送應用程式補償交易的簡單視覺化:
在 Orkes Conductor 中定義工作流程時,我們可以在主應用程式失敗時**觸發failureWorkflow
** 。在定義中,包含在應用程式失敗時要執行的工作流程名稱。
"failureWorkflow": "<name of the workflow to be run on failure>",
Orkes Conductor 中的補償工作流程會在發生故障時回滾變更:
當我們的主應用程式中的任何服務失敗時,此工作流程就會觸發。
假設由於資金不足導致支付失敗。然後,故障工作流程觸發,啟動補償流程,如下所示:
系統取消支付,隨後取消訂單,並向用戶發送失敗通知。
繁榮🎊!這就是我們如何使用 Orkes Conductor 回滾食品配送應用程式中已完成的事務,從而保持應用程式的一致性。
還有一個Slack 社區,可能是查看與 Conductor 相關的任何查詢的好地方。
六,結論
在本文中,我們使用 Orkes Conductor 和 Java Spring Boot 3 成功開發了一個訂單管理應用程序,實現了 Saga 模式。
Orkes Conductor可在所有主要雲端平台上使用:AWS、Azure 和 GCP。
與往常一樣,本文的源代碼可以在 GitHub 上取得。