事务 - Saga模式

Saga

githubgit

1987年普林斯顿大学的Hector Garcia-Molina和Kenneth Salem发表了一篇Paper Sagas,讲述的是如何处理long lived transaction(长活事务)。听起来是否是以为和分布式事务很像?没错,下面来看看这个来自1987年的解决方案是如何启发当今的分布式事务问题的。github

协议介绍

Saga的组成:apache

  • 每一个Saga由一系列sub-transaction Ti 组成
  • 每一个Ti 都有对应的补偿动做Ci,补偿动做用于撤销Ti形成的结果

能够看到,和TCC相比,Saga没有“预留”动做,它的Ti就是直接提交到库。segmentfault

Saga的执行顺序有两种:分布式

  • T1, T2, T3, ..., Tn
  • T1, T2, ..., Tj, Cj,..., C2, C1,其中0 < j < n

Saga定义了两种恢复策略:中间件

  • backward recovery,向后恢复,即上面提到的第二种执行顺序,其中j是发生错误的sub-transaction,这种作法的效果是撤销掉以前全部成功的sub-transation,使得整个Saga的执行结果撤销。
  • forward recovery,向前恢复,适用于必需要成功的场景,执行顺序是相似于这样的:T1, T2, ..., Tj(失败), Tj(重试),..., Tn,其中j是发生错误的sub-transaction。该状况下不须要Ci

对于ACID的保证

Saga对于ACID的保证和TCC同样:接口

  • A,正常状况下保证。
  • C,在某个时间点,会出现A库和B库的数据违反一致性要求的状况,可是最终是一致的。
  • I,在某个时间点,A事务可以读到B事务部分提交的结果。
  • D,和本地事务同样,只要commit则数据被持久。

和TCC对比

Saga相比TCC的缺点是缺乏预留动做,致使补偿动做的实现比较麻烦:Ti就是commit,好比一个业务是发送邮件,在TCC模式下,先保存草稿(Try)再发送(Confirm),撤销的话直接删除草稿(Cancel)就好了。而Saga则就直接发送邮件了(Ti),若是要撤销则得再发送一份邮件说明撤销(Ci),实现起来有一些麻烦。事务

若是把上面的发邮件的例子换成:A服务在完成Ti后当即发送Event到ESB(企业服务总线,能够认为是一个消息中间件),下游服务监听到这个Event作本身的一些工做而后再发送Event到ESB,若是A服务执行补偿动做Ci,那么整个补偿动做的层级就很深。ci

不过没有预留动做也能够认为是优势:资源

  • 有些业务很简单,套用TCC须要修改原来的业务逻辑,而Saga只须要添加一个补偿动做就好了。
  • TCC最少通讯次数为2n,而Saga为n(n=sub-transaction的数量)。
  • 有些第三方服务没有Try接口,TCC模式实现起来就比较tricky了,而Saga则很简单。
  • 没有预留动做就意味着没必要担忧资源释放的问题,异常处理起来也更简单(请对比Saga的恢复策略和TCC的异常处理)。

实现Saga的注意事项

对于服务来讲,实现Saga有如下这些要求:

  1. Ti和Ci是幂等的。
  2. Ci必须是可以成功的,若是没法成功则须要人工介入。
  3. Ti - Ci和Ci - Ti的执行结果必须是同样的:sub-transaction被撤销了。

第一点要求Ti和Ci是幂等的,举个例子,假设在执行Ti的时候超时了,此时咱们是不知道执行结果的,若是采用forward recovery策略就会再次发送Ti,那么就有可能出现Ti被执行了两次,因此要求Ti幂等。若是采用backward recovery策略就会发送Ci,而若是Ci也超时了,就会尝试再次发送Ci,那么就有可能出现Ci被执行两次,因此要求Ci幂等。

第二点要求Ci必须可以成功,这个很好理解,由于,若是Ci不能执行成功就意味着整个Saga没法彻底撤销,这个是不容许的。但总会出现一些特殊状况好比Ci的代码有bug、服务长时间崩溃等,这个时候就须要人工介入了。

第三点乍看起来比较奇怪,举例说明,仍是考虑Ti执行超时的场景,咱们采用了backward recovery,发送一个Ci,那么就会有三种状况:

  1. Ti的请求丢失了,服务以前没有、以后也不会执行Ti
  2. Ti在Ci以前执行
  3. Ci在Ti以前执行

对于第1种状况,容易处理。对于第二、3种状况,则要求Ti和Ci是可交换的(commutative),而且其最终结果都是sub-transaction被撤销。

参考资料

相关文章
相关标签/搜索