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适用的场景较多,长事务适用,对中间结果不敏感的业务场景适用。