分布式事务 - Saga
Saga简介
SAGA最初出现在1987年Hector Garcaa-Molrna & Kenneth Salem发表的论文SAGAS里。其核心思想是将长事务拆分为多个短事务,由Saga事务协调器协调,如果每个短事务都成功提交完成,那么全局事务就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。
Saga是一种在微服务架构中维护数据一致性的机制,它通过使用异步消息来协调一系列本地事务,从而维护多个服务之间的数据一致性。 Saga不依赖底层数据库事务的回滚机制,而是调用用户编写的补偿事务来回滚。
当本地事务完成时,服务会发布消息,从而触发Saga中的下一个步骤。
使用异步消息的好处除了确保Saga参与方之间松散耦合,还可以保证Saga完成,因为如果消息接收方暂时不可用,消息代理会缓存消息,直到最终投递。
Saga的协调模式
- 协同式:把Saga的决策和执行顺序逻辑分布在Saga的每一个参与方中,它们通过交换事件的方式来沟通。
- 优点:简单,松耦合
- 缺点:逻辑分散在各个服务,更难理解,容易导致服务间循环依赖。
- 编排式:把Saga的决策和执行顺序逻辑集中在一个Saga编排器中。Saga编排器调用Saga个参与方完成具体操作。
- 优点:无循环依赖,较少的耦合,改善关注点隔离,简化业务逻辑。
除了最简单情况,对于长事务,都建议采用编排式Saga。
Saga的隔离问题
Saga与ACID事务不同的是,它缺少ACID事务的隔离性,因为Saga中每个步骤中的本地事务的提交,都会立即被其他事务看到。
可以认为Saga只满足ACD三个属性:
- 原子性:确保执行所有事务或通过补偿事务撤销所有更改;
- 一致性:服务内的参照完整性由本地数据库处理,服务间的参照完整性由服务处理;
- 持久性:由本地数据库处理;
缺乏隔离可能导致以下3种异常:
- 丢失更新:一个Saga没有读取更新,而是直接覆盖了另一个并发的Saga所做的更改。
- 脏读:一个事务读取了其他尚未完成的Saga所做的更新。
- 不可重复读:一个Saga不同的步骤读相同的数据获得了不同的结果,因为这个数据被另一个并发的Saga进行了更新。
解决方案:
- 语义锁:应用程序级的锁,防止其他并发事务修改一个尚未完成的saga涉及的数据。
- 交换式更新:把更新操作设计成可以按任何顺序执行。
- 悲观视图:重排Saga步骤,最大限度降低业务风险。
- 重读值:防止丢失更新,更新记录前先验证是否未更改。
- 版本文件
- 业务风险评级:比如使用Saga来执行低风险需求
Saga的结构
一个Saga包含3种类型的事务:
- 可补偿性事务。
- 关键性事务:关键点,它执行完后,Saga将一直运行到完成。
- 可重复性事务:关键性事务之后的事务,保证可以成功。
状态机是建模Saga编排器的一个好方法。
基于编排的Saga的每个步骤都包括一个更新数据库和发布消息的操作。服务必须使用事务性消息保证原子性。
Saga事务的特点
- 并发度高,不用像XA事务那样长期锁定资源;
- 需要定义正常操作以及补偿操作,开发量比XA大;
- 缺乏隔离性,事务执行过程中的中间状态对外界可见,比如对于转账业务,可能发生A用户已扣款,最后转账又失败,导致退回金额的情况;
- SAGA适用的场景较多,长事务适用,对中间结果不敏感的业务场景适用。