1. 引言
在典型的微服务架构中,单个业务用例往往跨越多个微服务,每个服务都有自己的本地数据存储和本地事务。当涉及多个事务且微服务数量庞大时,就需要处理跨服务的事务管理问题。Saga模式正是为解决这类分布式事务问题而生的。该模式最早由Hector Garcia Molina和Kenneth Salems在1987年提出,定义为一系列可相互交织的事务序列。
本文将深入探讨分布式事务管理的挑战、基于编排的Saga模式如何解决这些问题,并使用Spring Boot 3和Orkes Conductor(开源编排平台Conductor OSS的企业版)实现一个Saga模式示例。
2. 分布式事务管理的挑战
如果实现不当,分布式事务会带来诸多挑战。在分布式事务中,每个微服务都有独立的本地数据库,这种模式通常称为"每个服务一个数据库"(Database per Service)。
例如:
- 某个微服务可能因MySQL的性能特性选择它
- 另一个微服务可能因PostgreSQL的优势选择它
在这种模型中,每个服务执行自己的本地事务来完成整个应用事务。这个整体事务被称为分布式事务。
分布式事务有多种处理方式,传统方法包括两阶段提交(2PC)和ACID事务(原子性、一致性、隔离性、持久性),但它们都存在多语言持久化、最终一致性、延迟等挑战。
3. 理解Saga模式
Saga模式是一种架构模式,用于实现一系列本地事务,帮助维护不同微服务间的数据一致性。
每个本地事务更新自己的数据库后,通过发布消息或事件触发下一个事务。如果某个本地事务失败,saga会执行一系列补偿事务来回滚之前事务所做的更改。这确保了即使事务失败,系统仍能保持一致性。
流程包括:
- 用户下单
- 库存检查
- 支付处理
- 配送服务
- 通知服务
如果支付失败,系统必须执行补偿事务回滚之前步骤(如撤销支付、取消订单)。Saga模式能在任何阶段处理失败并补偿已完成的事务。
Saga模式有两种实现方式:
编排模式(Choreography):
协同模式(Orchestration):
4. 为什么选择基于协同的Saga模式?
编排模式的去中心化特性使服务交互的管理和监控更具挑战性。缺乏中央协调和可见性会增加复杂性,使应用更难维护。
4.1. 编排模式的局限性
构建分布式应用时,编排模式存在明显缺陷:
- ✅ 紧耦合:服务直接连接,任何服务变更可能影响所有连接服务
- ✅ 分布式事实源:跨微服务维护应用状态使流程跟踪复杂化,可能需要额外系统整合状态信息
- ✅ 故障排查困难:流程分散在不同服务中,定位和修复问题耗时更长
- ✅ 测试环境复杂:微服务相互连接使开发者测试困难
- ✅ 维护成本高:服务演进时引入新版本需添加条件逻辑,导致"分布式单体"问题
4.2. 协同模式的优势
协同模式在构建分布式应用时具有显著优势:
- ✅ 分布式系统内协调事务:中央协调器按预定义方式管理微服务执行,确保应用一致性
- ✅ 补偿事务:Saga模式在失败时能执行补偿事务,回滚已完成事务
- ✅ 异步处理:微服务可独立处理活动,协调器管理异步操作的通信和排序
- ✅ 可扩展性:通过添加或修改服务即可调整应用,不影响整体架构
- ✅ 增强可见性和监控:提供跨分布式应用的集中可见性,快速识别和解决问题
- ✅ 加速上市时间:简化服务重连和新流程创建,支持快速适应变化
总结:基于协同的Saga模式为微服务架构提供了协调、一致且可扩展的分布式事务实现方式,通过补偿事务处理故障,是构建健壮分布式应用的强大模式。
5. 使用Orkes Conductor实现Saga协同模式
下面通过一个实际示例展示如何使用Orkes Conductor实现Saga模式。考虑一个包含以下服务的订单管理系统:
- OrderService:处理初始订单创建(添加购物车商品、指定数量、初始化结账)
- InventoryService:检查并确认商品可用性
- PaymentService:安全处理支付流程(支持多种支付方式)
- ShipmentService:准备商品配送(打包、生成运单、启动配送)
- NotificationService:向用户发送订单更新通知
我们将使用Orkes Conductor和Spring Boot 3复制这个流程。
5.1. 外卖配送应用
使用Saga模式构建的外卖应用在Conductor UI中如下所示:
在Playground中查看
工作流进展:
- 用户下单触发一系列工作器任务:
- 添加商品到购物车(order_food)
- 检查餐厅商品可用性(check_inventory)
- 支付处理(make_payment)
- 配送处理(ship_food)
- 进入fork-join任务处理通知服务:
- 通知配送员
- 通知用户
5.2. 运行应用
前置条件:
Orkes Conductor设置选项:
本示例使用Playground。核心代码片段如下:
@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;
}
}
运行步骤:
更新application.properties文件(使用生成的访问密钥):
conductor.server.url=https://play.orkes.io/api conductor.security.client.key-id=<key> conductor.security.client.secret=<secret>
- 使用Playground时conductor.server.url保持不变
- 替换key-id和secret为实际密钥
- 为工作器添加访问工作流和任务的权限
- 默认conductor.worker.all.domain为'saga',建议修改为其他名称避免冲突
运行应用:
gradle bootRun
调用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中可视化应用流程(导航至"Executions > Workflow"搜索ID)。
5.3. 补偿流程
在Orkes Conductor中定义工作流时,可通过failureWorkflow指定主应用失败时触发的补偿工作流:
"failureWorkflow": "<失败时运行的工作流名称>",
补偿工作流在失败时回滚更改:
在Playground中查看
当主应用中任何服务失败时触发此工作流。例如支付失败(余额不足)时:
系统会:
- 取消支付
- 取消订单
- 向用户发送失败通知
这就是使用Orkes Conductor回滚已完成事务的方式,确保应用一致性。遇到Conductor相关问题可加入Slack社区咨询。
6. 结论
本文成功使用Orkes Conductor和Java Spring Boot 3开发了一个订单管理应用,实现了Saga模式。Orkes Conductor支持所有主流云平台:AWS、Azure和GCP。
本文源代码可在GitHub获取。