这是分布式事务系列的第五篇,如果之前文章没读请自行前往。精华专题,强烈建议收藏。
本文详细讲解了分布式事务解决方案——SAGA。
SAGA事务
什么是SAGA事务
SAGA 的意思是“长篇故事、长篇记叙、一长串事件”。SAGA 事务模式的提出非常早,甚至早于分布式事务概念的提出。
SAGA 于 1987 年由普林斯顿大学的 Hector Garcia-Molina 和 Kenneth Salem 在 ACM 发表的论文《SAGAS》中提出。
这篇论文讲述的核心是如何处理长时间活跃的事务,SAGA 指出可将其拆分成可以交错运行的子事务集合,每个子事务都是一个真实的事务,子事务可以独自保证数据一致性。
为什么需要SAGA
之前我们介绍了 TCC 分布式事务解决方案,它拥有诸多优点:更强的一致性、更强的个理性、更好的性能,但是他也有一个显著的缺点就是业务侵入性强。业务侵入性强并不只是说我们编码麻烦而已,有时候业务不是你想侵入就侵入的,比如其他部门不愿意配合,比如使用三方系统。
依旧是以我们熟悉的电商业务为例子,需要经过下单、余额支付、库存扣件这三个过程,之前我们已经用TCC实现了这个过程。假设现在新增一个业务场景,余额支付需要替换为直接使用绑定的银行卡付款,现在又怎么做?银行并不像是我们的自有系统一样,可以提供专门的接口供我们去将本次事务所需的资源占用、资源提交、以及资源回滚。
如果不能用TCC,怎么办?这种情况下,SAGA 就有了用武之地。
SAGA 内容
SAGA 基本协议内容如下:
每个 SAGA 事务都由一系列的有序子事务(sub-transaction) T1,T2,…,Ti,…,Tn组成,每个事务都支持幂等。 每个 Ti 都有对应的补偿动作Ci,比如 C1,C2,…,Ci,…,Cn,补偿动作用于撤销 T1,T2,…,Ti,…,Tn造成的影响。同样,补偿操作也需要支持幂等。
如果 T1 到 Tn 均成功提交,那么事务就顺利完成。只有有一个环节出现失败就要采取恢复策略。
恢复策略分为向前恢复和向后恢复两种,具体使用那种方案需要根据实际场景选择。
向前恢复(Forward Recovery)
如果某个环节的Ti事务提交失败,那么就对这个 Ti 事务不断进行重试(接口要幂等),直到接口返回成功。
正向恢复不需要对应的补偿动作Ci,适用于在业务上都为正向操作的场景。继续以电商为例,如果用户的订单付款成功,那就一定要发货。
顺序为 T1、T2、T3 (失败,不断尝试,继续执行)、T4……
如下图:
向后恢复(Backward Recovery)
如果某个环节的Ti事务提交失败,那么就执行这个 Ti 事务对应的补偿Ci操作,不断进行重试(Ci接口要幂等),直到接口返回成功。
这里要求 Ci 必须可以执行成功,适用于在业务上允许失败的场景。继续以电商为例,如果用户的订单支付失败,那么需要支付、订单和库存都执行补偿。
顺序为 T1、T2、T3(失败)、C3、C2、C1
如下图:
实现SAGA
实现SAGA注意事项
如果实现SAGA,有三个需要注意的点:
Ti和Ci是幂等的,因为都需要重试以保证终一致性。 Ci 必须是能够成功的,否则SAGA将无法撤销影响,如果无法成功则需要人工介入做补偿。 Ti 和Ci的执行顺序可以交换,不保证Ti一定在Ci前执行,但是终执行效果相同,即子事务Ti影响被撤销。
这里着重说一下第三点,为什么要求Ci可以在Ti之前执行?因为网络之间的不稳定性,无法保证Ti一定执行,或者一定在Ci之前执行。具体来说,有以下三种执行情况。
正常情况:Ti先执行,Ci后执行。 Ti请求因为网络问题丢失了,彻底不会执行。 Ti执行超时,判断为执行失败,直到Ci执行前 Ti 都没有执行完成,导致出现Ci先执行的情况。
所以,有了第三点的要求,Ti 和 Ci 顺序可交换。
两种模式
通过阅读SAGA协议的具体内容,我们可以发现,实现的关键之一子事务之间的协调。首先我们要知道一个事务的开始,并且可以让子事务按顺序执行,并且在某个事务执行完后通知下一个子事务。如果有子事务执行失败,需要按照顺序执行补偿逻辑。
所以根据协调子事务的方法,可以分为两类:命令协调模式和事件编排模式。
编排(Choreography): 控制(Orchestration):
命令协调模式(Orchestration)
命令协调模式:由中央协调器集中处理事件的决策和业务逻辑排序,以命令或者回复的方式与每个参与服务进行通信,全权负责告诉每个参与者什么时候该做什么。
依旧以之前的电商为例:
事务发起者调用SAGA控制器开启事务(这里的控制器也可以由发起者兼任)。 SAGA中央协调器发起扣减库存,库存扣减结果返回。 SAGA中央协调器发起创建订单,订单创建结果返回。 SAGA中央协调器发起支付请求,支付结果返回。 SAGA中央协调器处理终结果,并返回给应用程序。
SAGA中央协调器预先知道完整的事务处理流程,这可以通过配置实现。如果任意子事务失败失败,它便向每个参与者发送命令来执行补偿操作Ci(或者执行重试-向前恢复)。
其缺点很明显,既然有中央协调器就会有单点问题。
但是优点也很多,主要有:服务之间的编排顺序明确,依赖关系简单,不会有循环依赖;耦合相对较少,参与者只需要依赖协调者接口,参与者之间没有直接依赖;参与者只需要关注自身业务,服务之间协调统一由协调器管理。
事件编排模式
事件编排模式:SAGA 中的参与者通过交换事件进行沟通,按照提前编排好的发布顺序决策和排序。
这种模式没有单点风险,由每个服务监听对应事件,并对事件做出反应。SAGA事务由应用程序发布个事件开始,中间服务接受到对应事件做本地事务的处理,然后继续发布事件。每一个事件由一个或者多个服务监听。当后一个服务执行本地事务并发布事件后,Application 收到后一个事件,事务结束,事件处理的顺序都是提前编排好的。具体参考下图:
电商订单的例子为例:
应用程序发起SAGA事务,以订单事件发布开始。 库存服务监听开始订单事件,扣减库存,成功后发布库存扣减事件。 订单服务监听库存扣减事件,创建订单,并发布订单已创建事件。 支付服务监听订单创建事件,进行支付,并发布订单已支付事件。 应用程序监听支付成功事件,SAGA事务结束。
事件/编排是实现 SAGA 模式的自然方式,它通过事件串联各个服务,实现了服务之间的松耦合。并且实现简单,只需要在执行本地事务时发布事件。如果事务涉及 2 至 4 个步骤,则可能是非常合适的。
但是有一些缺点:因为代码中没有明显的编排逻辑,所以可能会比较难理解;服务之间可能会有循环依赖;因为需要订阅事件,所以随着服务的变更可能有漏定的风险,每次需要明确评估影响,保持下游业务订阅的完整性。
SAGA 实践
SAGA 使用条件
SAGA 的使用上有一些限制条件:
SAGA 只允许两个层次的嵌套,的 SAGA 事务和简单子事务。 每个子事务之间是独立的,各自保证原子性。 全局SAGA之间无法保证隔离性。 补偿事务Ci只能从语义或者业务角度撤消了事务Ti的行为,但未必能将数据库返回到执行Ti时的状态。
关于补偿,说明一下。比如用户使用了红包来支付,但是部分订单退款,这时候我们无法补发原价值的红包。但是我们可以在业务上进行补偿,比如重新拍发一个对应退款金额的新红包。
ACID特性保证
SAGA 不提供ACID保证,因为原子性和隔离性不能得到满足,具体如下。
原子性(Atomicity):只能业务上保证,不是严格的原子性。 隔离性(Isolation):不能保证,不同SAGA事务之间中间结果可见。 一致性(Consistency):保证终一致性,但是中间状态会不一致。 持久性(Durability):可以保证。
TCC对比
看起来和TCC很相似,但是和TCC相比,SAGA 没有“预留”动作,每个子事务的 T 操作直接提交数据。 因为没有预留数据,所欲 TCC 可以保证隔离性,但是 SAGA不行。 但也因为没有预留动作,SAGA 在一些场景下实现简单,并且少一次网络通信过程。 SAGA适合无法提供 Try 接口的场景(比如对接银行),这点TCC无法做到。
适用场景
事务参与者含第三方或者无法提供TCC预留接口。
事务流程长,涉及系统多。
常用于银行、金融、贷款,或者技术架构不统一的复杂分布式场景。
优缺点
优点:
性能较高,无需锁定资源,子事务直接提交。
采用事件驱动模式,吞吐量更高。
缺点:
无法提供原子性和隔离性保证。 有一定业务侵入性,逆向接口不一定好实现。 如果采用命令协调模式有单点风险,需要做好日志记录和重试。
后,欢迎大家提问和交流。