Saga模式与PHP:微服务中分布式事务的精妙协调
阅读时间:约45分钟
想象一下通过移动应用订购披萨的场景。在幕后,一系列微服务协同工作:验证您的账户、在卡上预留资金、在餐厅系统中创建订单、通知配送服务、扣除积分。当其中一个步骤出现问题时会发生什么?这就是Saga模式登场的时候——一个优雅的分布式事务管理解决方案。
理论基础
CAP定理与分布式系统
在深入了解Saga之前,理解我们所处的理论约束至关重要。由Eric Brewer提出的CAP定理指出,在任何分布式数据存储中,您只能同时保证以下三个属性中的两个:
- 一致性(C):所有节点同时看到相同的数据
- 可用性(A):系统保持运行和响应
- 分区容错性(P):系统在网络故障时继续运行
在微服务架构中,网络分区是不可避免的,因此我们必须在一致性和可用性之间做出选择。Saga模式本质上是通过提供最终一致性同时保持高可用性来管理这种权衡。
ACID vs BASE属性
传统数据库提供ACID保证:
- 原子性:全有或全无执行
- 一致性:有效状态转换
- 隔离性:并发操作互不干扰
- 持久性:提交的更改持久存在
分布式系统通常采用BASE属性:
- 基本可用:系统在故障时保持可用
- 软状态:状态可能随时间变化而无需输入
- 最终一致性:系统最终将变得一致
Saga模式通过以即时一致性换取可用性和分区容错性来体现BASE原则。
分布式系统中的事务模型
两阶段提交(2PC)
传统的2PC协议试图在分布式系统中维护ACID属性:
- 准备阶段:协调者要求所有参与者准备
- 提交阶段:如果全部同意,协调者告诉所有参与者提交
2PC的问题:
- 阻塞协议(单点故障)
- 由于同步性质导致性能差
- 无法优雅处理协调者故障
- 长时间锁定资源
三阶段提交(3PC)
3PC添加"预提交"阶段以减少阻塞场景,但引入了额外的复杂性和网络开销。
Saga事务
Saga模式采用根本不同的方法:
- 补偿事务:使用可逆操作而不是锁
- 前向恢复:完成saga或补偿已完成的步骤
- 无全局锁:每个步骤都是本地事务
- 异步:非阻塞执行模型
为什么ACID在微服务中不起作用
在单体应用中,我们习惯于ACID事务的舒适性。数据库保证要么所有操作成功,要么都不成功。但在微服务架构中,每个服务都有自己的数据库,传统事务变得无能为力。
|
|
问题很明显:我们无法保证跨不同系统分布的操作的原子性。
Saga模式:分而治之
Saga模式优雅地解决了这个问题:不是使用一个大型事务,而是创建一系列本地事务,每个事务在失败时都可以被补偿。
核心概念
1. 可补偿事务
可以使用补偿操作"撤销"的操作:
- 预留资金 ↔ 释放资金
- 创建订单 ↔ 取消订单
- 发送邮件 ↔ 发送取消邮件
2. 枢轴事务(不归点)
在此之后saga必须成功完成的操作。通常是不可逆的操作,如扣款信用卡或发货。
3. 可重试事务
可以安全重复直到成功完成的幂等操作。
4. 关键部分
必须在单个服务边界内原子执行的saga部分。
数学模型
Saga S可以表示为事务序列:
|
|
每个事务Tᵢ都有相应的补偿事务Cᵢ:
|
|
对于成功执行:
|
|
对于步骤k的失败执行:
|
|
其中•表示顺序组合。
实现方法
1. 编排:服务的舞蹈
在编排方法中,服务通过事件交互,没有中央协调器:
|
|
编排优点:
- 无单点故障
- 简单的小型系统
- 高性能
- 自然解耦
编排缺点:
- 随着系统增长调试复杂
- 循环依赖风险
- 测试困难
- 难以维护全局不变量
2. 协调:指挥管理乐团
在协调方法中,中央协调器管理整个过程:
|
|
协调优点:
- 集中控制和可见性
- 更容易调试和测试
- 清晰的业务流程
- 更好地处理复杂工作流
协调缺点:
- 单点故障(协调器)
- 潜在性能瓶颈
- 协调器与服务之间的紧耦合
高级实现技术
Saga状态和持久化
|
|
处理超时和恢复
|
|
幂等性和去重
|
|
理论挑战和解决方案
数据一致性异常
ABA问题
在分布式系统中,值可能在观察之间从A变为B再变回A。Saga模式通过以下方式解决此问题:
- 版本向量:跟踪因果关系
- 逻辑时间戳:一致地排序事件
- 乐观锁:检测并发修改
|
|
丢失更新问题
当多个saga并发修改同一资源时: 解决方案:语义锁定
|
|
脏读问题
从其他saga读取未提交的数据: 解决方案:可交换更新
|
|
高级理论模式
嵌套Saga
复杂的业务流程可能需要分层saga结构:
|
|
带确认的Saga模式
需要外部批准的操作:
|
|
Saga中的并行分支
可以并发执行的操作:
|
|
形式验证和测试
基于属性的测试
Saga实现应满足某些不变量:
|
|
监控和可观察性
全面的Saga跟踪
|
|