SAGE核心思想是将长事务拆分为多个本地短事务,由Saga事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作
- 如何实现补偿(提前准备回滚语句)
- dtm 的SAGA模式与Seata的SAGA在设计理念上是不一样的
流程
已跨行转账的业务为例,转出(TransOut
)和转入(TransIn
)分别在不同的微服务里,一个成功完成的SAGA事务典型的时序图如下
成功

失败

代码如下:
1 | req := &gin.H{"amount": 30} // 微服务的请求Body |
- 构建了事务的请求Body
- 构建一个事务包含了
DtmServer
为DTM服务的地址shortuuid.New()
事务请求ID- 添加一个
TransOut
的子事务,每个事务都包含了正向操作与补偿操作(逆向操作)- 正向操作为url:
qsBusi+"/TransOut"
, - 逆向操作为url:
qsBusi+"/TransOutCompensate"
- 正向操作为url:
- 添加一个
TransIn
的子事务- 正向操作为url:
qsBusi+"/TransIn"
- 逆向操作为url:
qsBusi+"/TransInCompensate"
- 正向操作为url:
- 提交saga事务
问题1:是如何进行补偿的
问题2:补偿失败是如何处理的
答:在补偿操作遇见失败时,会不断进行重试,直到成功。(TM重启了怎么办)
问题3:sage事务是同步返回结果还是异步任务处理的
问题4:当RM1执行成功,RM2执行失败的同时RM1崩溃了,TM会如何处理
问题5:当服务崩溃大量事务堆积在TM上,TM如何支持短时间大量事务重试
补偿
补偿的情况有几种
- 第一种情况,子事务 A - B - C 中 C 失败,需要对 A - B 进行补偿操作,如何保存处理单个分布式事务的子事务顺序补偿问题 B 先回滚,然后是A
- 第二种情况,子事务 A - B - C 在执行过程中 A1 - B1 - C1 也在执行,A - B - C 中 C失败,A1 - B1 - C1 中 B1 失败,那么如何处理
- 单个服务的补偿分为 已执行 、未执行、执行中(结果未知),那么补偿又是如何处理的
补偿执行顺序
DTM 的SAGA事务在1.10.0及之前,补偿操作是并发执行的,1.10.1之后,是根据用户指定的分支顺序,进行回滚的。
-
普通SAGA,未打开并发选项,那么SAGA事务的补偿分支是完全按照正向分支的反向顺序进行补偿的。
-
并发SAGA,补偿分支也会并发执行,补偿分支的执行顺序与指定的正向分支顺序相反。假如并发SAGA指定A分支之后才能执行B,那么进行并发补偿时,DTM保证A的补偿操作在B的补偿操作之后执行
Demo
一个用户出行旅游的应用,收到一个用户出行计划,需要预定去三亚的机票,三亚的酒店,返程的机票。要求:
- 两张机票和酒店要么都预定成功,要么都回滚(酒店和航空公司提供了相关的回滚接口)
- 预订机票和酒店是并发的,避免串行的情况下,因为某一个预定最后确认时间晚,导致其他的预定错过时间
- 预定结果的确认时间可能从1分钟到1天不等
首先,根据要求1创建一个saga事务,这个saga包含三个分支,预定去三亚机票
,预定酒店
,预定返程机票
1 | saga := dtmcli.NewSaga(DtmServer, gid). |
接着,根据要求2,让saga并发执行(默认是顺序执行)
1 | saga.EnableConcurrent() |
最后,根据要求3,由于不是即时响应,所以不能够让预定操作等待第三方的结果,而是提交预定请求后,就立即返回状态-进行中
1 | saga.RetryInterval = 60 |
分支事务未完成,dtm会重试我们的事务分支,把重试间隔指定为1分钟,这里订票结果不应当采用指数退避算法重试,否则最终用户不能及时收到通知。在
bookTicket
中,返回结果ONGOING,当dtm收到这个结果时,会采用固定间隔重试,这样能及时通知到用户
并发
并发SAGA通过EnableConcurrent()
打开,当saga提交后,多个事务分支之间是并发执行。DTM也支持指定事务分支之间的依赖关系,可以指定特定任务A执行完成之后才能够执行任务B
并发SAGA如果出现回滚,那么所有回滚的补偿操作会全部并发执行,不再考虑前面的任务依赖。
由于并发SAGA的正向操作和补偿操作都是并发执行的,因此更容易出现空补偿和悬挂情况,需要参考DTM的子事务屏障环节妥善处理
部分无法回滚
1 | saga := dtmcli.NewSaga(DtmServer, shortuuid.New()). |
指定Step 2,3 中的 UnRollback 操作,必须在Step 0,1 完成后执行
这样也能处理 第一个事务输出是第二个事务的输入怎么办的问题
超时回滚
saga属于长事务,因此持续的时间跨度很大,可能是100ms到1天,因此saga没有默认的超时时间。
dtm支持saga事务单独指定超时时间,到了超时时间,全局事务就会回滚。
1 | saga.TimeoutToFail = 1800 |
在saga事务中,设置超时时间一定要注意,这类事务里不能够包含无法回滚的事务分支,因为超时回滚时,已执行的无法回滚的分支,数据就是错的