1. 引言
在典型的微服務架構中,當單個業務用例跨越多個微服務時,每個服務都有其本地數據存儲和本地化事務。對於多個事務,以及微服務的數量龐大時,就產生了處理跨多個服務的事務的需求。
Saga 模式被引入用於處理這些多個事務。最初由 Hector Garcia Molina 和 Kenneth Salems 於 1987 年引入,它被定義為可以相互交錯的事務序列。
在本教程中,我們將深入瞭解管理分佈式事務的挑戰,如何基於編排的 Saga 模式解決這個問題,以及使用 Spring Boot 3 和 Orkes Conductor 的 Saga 模式的示例實現,Orkes Conductor 是領先的開源編排平台,該平台的企業級版本 Conductor OSS (前身為 Netflix Conductor)。
2. 分佈式事務的管理挑戰
分佈式事務如果實施不當,會帶來很多挑戰。在分佈式事務中,每個微服務都有獨立的本地數據庫。這種方法通常被稱為“服務專用數據庫”模型。
例如,由於 MySQL 的性能特性和功能,可能適合用於一個微服務,而 PostgreSQL 則可能根據其優勢和能力選擇用於另一個微服務。在這個模型中,每個服務都會執行其本地事務以完成整個應用程序事務。這個整個事務被稱為分佈式事務。
分佈式事務可以通過多種方式處理。傳統的兩種方法是 2PC(兩階段提交)和 ACID(原子性、一致性、隔離性和持久性)事務,每種方法都存在自身的挑戰,例如 多重持久性、最終一致性、延遲等。
3. 理解 Saga 模式
Saga 模式是一種用於實現一系列本地事務的架構模式,有助於在不同的微服務之間保持數據一致性。
本地事務更新其數據庫並通過發佈消息或事件觸發下一個事務。如果本地事務失敗,Saga 將執行一系列補償事務以回滾先前事務所做的更改。這確保了即使事務失敗,系統也能保持一致性。
為了進一步説明這一點,請考慮一個訂單管理系統,它包含從下單到交付訂單的連續步驟:
在這個例子中,流程從應用程序中用户下單開始。流程隨後通過幾個步驟:庫存檢查、支付處理、運輸和通知服務。
如果支付失敗,應用程序必須執行補償事務以回滾先前步驟所做的更改,例如撤銷支付和取消訂單。這確保了 Saga 模式可以在任何階段處理失敗並補償先前事務。
Saga 模式可以以兩種不同的方式實現。
編排: 在這種模式中,單個微服務會消費事件、執行活動並將事件傳遞給下一個服務。沒有中心協調器,這使得服務之間的通信更加困難:
管絃: 在這種模式中,所有微服務都與中心協調器相連,該協調器以預定義的順序協調服務,從而完成應用程序流程。這有助於可見性、監控和錯誤處理:
4. 編排模式為何優於基於編排的模式?
由於編排模式的去中心化方法,管理和監控服務交互變得更加困難。缺乏集中協調和可見性導致複雜性增加,使得應用程序更難維護。
讓我們來看看編排模式的主要缺點以及選擇編排模式的優勢。
4.1. 編排的侷限性
基於編排的實現在構建分佈式應用程序時存在諸多侷限性:
- 緊耦合 – 服務之間緊密耦合,因為它們直接連接。應用程序中對任何一個服務的修改都可能影響所有連接的服務,升級服務時需要依賴性管理。
- 分佈式數據源之真 – 在各個微服務中維護應用程序狀態使得跟蹤流程變得複雜,可能需要額外的系統來彙總狀態信息。這增加了基礎設施併為整體系統帶來了複雜性。
- 難以調試 – 當應用程序流程分佈在不同的服務中時,查找和修復問題會花費更長的時間。調試需要集中式日誌服務以及對代碼的深入理解。如果一個服務失敗,可能會導致更嚴重的問題,甚至可能造成廣泛的中斷。
- 難以進行測試 – 由於微服務相互連接,測試變得困難。
- 難以維護 – 隨着服務的演進,引入新版本需要重新引入條件邏輯,再次導致分佈式單體應用。這使得在不檢查整個代碼的情況下理解服務流程變得更加困難。
4.2. 編排的優勢
編排式實現在構建分佈式應用程序時具有許多優勢:
- 分佈式系統內的協調事務 – 不同的微服務處理分佈式系統中的不同事務方面。在編排式模式下,中央協調器以預定義的⽅式管理這些微服務的執行,並積極確保單個本地事務的精確執行,從而維護應用程序的一致性。
- 補償事務 – 在應用程序中,由於任何錯誤,執行的任何點都可能發生故障。 。這提高了生產力,減少了停機時間,並最終降低了檢測和從故障中恢復的平均時間。
- 更快的上市時間 – 協調器簡化了現有服務的重新配置和新流程的創建,從而促進了快速適應。這使應用程序團隊能夠更具敏捷性,從而加快了新想法和概念的上市時間。此外,協調器通常管理版本,減少了代碼中大量的 “if..then..else” 語句,用於創建不同版本的需求。
總結:編排式 Saga 模式提供了一種在微服務架構中實現協調、一致和可擴展的分佈式事務的方法,並且通過補償事務處理故障,使其成為構建健壯和可擴展的分佈式應用程序的強大模式。
5. 使用 Orkés Conductor 實施 Saga 編排模式
現在,讓我們來看一個使用 Saga 模式和 Orkés Conductor 的實際應用示例。
考慮一個訂單管理系統,包含以下服務:
- OrderService – 處理初始訂單放置,包括將項目添加到購物車、指定數量和初始化結賬流程。
- InventoryService – 檢查和確認項目可用性。
- PaymentService – 安全地管理支付流程,處理各種支付方式。
- ShipmentService – 準備項目進行運輸,包括包裝、生成運輸標籤和啓動運輸流程。
- NotificationService – 向用户發送關於訂單更新的通知。
讓我們使用 Orkés Conductor 和 Spring Boot 3 複製這個流程。
在開始應用開發之前,請確保系統滿足以下先決條件。
要為我們的應用程序設置 Orkés 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 呈現的食品外賣應用程序示例:
在 Playground 中查看
讓我們看看工作流程的進展情況:
- 應用程序開始於用户在食品外賣應用程序上下單。初始流程被實現為一個一系列 工件任務,其中包括將食物添加到購物車(order_food)、檢查餐廳的食物可用性(check_inventory)、支付流程(make_payment)和送貨流程(ship_food)。
- 應用程序流程隨後移動到 分支合併任務,該任務處理通知服務。它有兩個分支,一個通知送貨員,另一個告知用户。
現在,讓我們運行應用程序!
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。
- 為了使 Worker 與 Conductor 服務器連接,我們需要提供權限(在剛剛創建的應用程序中),以便訪問工作流程和任務。
- 默認情況下,conductor.worker.all.domain 設置為 ‘saga’。請確保更新為不同的名稱,以避免與 Orkes Playground 中其他人啓動的工作流程和 Worker 衝突。
使用以下命令從根項目運行應用程序:
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”。
一個示例執行情況如下:
讓我們看看如果某個服務失敗時應用程序流程會發生什麼。
5.3. 補償流程
以下是對食品外賣應用補償交易的簡化可視化:
在 Orkes Conductor 中定義工作流時,當主應用程序失敗時,我們可以觸發 failureWorkflow。在定義中包含要運行的工作流名稱,以防主應用程序失敗。
"failureWorkflow": "<name of the workflow to be run on failure>",Orkes Conductor 在發生故障時,會回滾補償流程中的變更:
此流程在我們的主應用程序中的任何服務失敗時觸發。
假設由於資金不足導致支付失敗。 那麼,故障流程會觸發,啓動補償流程如下:
系統取消支付,隨後取消訂單,並將失敗通知發送給用户。
砰 🎊! 這樣,我們使用 Orkes Conductor 在我們的食品外賣應用程序中回滾已完成的交易,從而保持應用程序的一致性。
此外,還有一個 Slack 社區 可供查閲,其中包含與 Conductor 相關的任何問題。
6. 結論
在本文中,我們成功地使用 Orkes Conductor 和 Java Spring Boot 3 開發了一個訂單管理應用程序,並實現了 Saga 模式。
Orkes Conductor 可在所有主流雲平台上使用:AWS、Azure 和 GCP。
如往常一樣,本文的源代碼可在 GitHub 上找到:GitHub。