开发者社区> IT枫斗者> 正文

深度剖析Saga分布式事务

简介: saga是分布式事务领域非常重要的事务模式,特别适合解决旅游订票等长期事务。本文将深入分析saga事务的设计原理和解决订票问题的最佳实践。
+关注继续查看

saga是分布式事务领域非常重要的事务模式,特别适合解决旅游订票等长期事务。本文将深入分析saga事务的设计原理和解决订票问题的最佳实践。

saga的理论来源
这种事务模式saga最早来自这篇论文。
http://www.amundsen.com/downloads/sagas.pdf

在这篇论文中,作者提出将一个长事务分为多个子事务,每个子事务都有正向操作Ti和反向补偿操作Ci。

如果Ti依次成功完成所有子事务,则全局事务完成。

如果子事务Ti失败,将调用Ci、Ci-1、Ci-2..进行补偿。

论文阐述了上述部分的基本saga逻辑后,提出了以下场景的技术处理。

回滚与重试
如果一项SAGA事务在执行过程中失败,那么接下来有两种选择,一种是回滚,另一种是继续重试。

回滚机制比较简单,下一步的操作只需要将下一步的操作记录到保存点即可。一旦出现问题,从保存点回滚,反向执行所有补偿操作。

如果一个持续了一天的长期事务被服务器重启等临时失败中断,如果此时只能回滚,业务是不可接受的。这时候最好的策略就是在保存点重试,让事务继续,直到事务完成。
重试的支持需要提前安排和保存整体事务的所有子事务,然后在失败时重新读取未完成的进度,继续重试。

并发执行
对于长期事务来说,并发执行的特点也很重要。在并行支持下,一个串行耗时一天的长期事务可能需要半天时间才能完成,对业务帮助很大。

在某些场景下,并发执行子事务是业务的必要要求,比如订多张票,机票确认时间长的时候,不要等前一张票确认后再订票,会导致订票成功率大幅下降。

在子事务并发执行的情况下,支持回滚和重试将面临更大的挑战,涉及更复杂的保存点。

saga的实现分类
目前市面上已经实现了很多saga,都有saga的基本功能。

这些实现大致可以分为两类。

状态机实现
这种典型的seatasaga实现了,他引入了DSL语言定义状态机,允许用户做以下操作:

子事务结束后,根据子事务的结果,决定下一步该怎么办。
能够将子事务执行的结果保存到状态机中,并作为后续子事务的输入。
允许不依赖的子事务并发执行。
这种方法的优点是:

功能强大,事务可以灵活定义。
缺点是:

状态机的使用门槛很高,需要了解相关DSL,可读性差,问题难以调试。官方例子是一个全局事务,包括两个子事务。Json格式状态机定义95行左右,很难入门。
接口入侵强,只能使用特定的输入输出接口参数类型。在云原生时代,对强型GRPC不友好(GRPC协议,TM无法获得用户定制的输入输出pb文件,无法分析结果中的字段)
非状态机实现
有eventuate的saga,dtm的saga。

在这种实现中,没有引入新的DSL来实现状态机,而是使用函数接口来定义整体事务下的所有分支事务:

优点:

易上手,易维护。
缺点:

很难灵活定义状态机的事务。
PS:eventuate的作者将基于事件订阅合作模式,也称为saga。因为他的影响力很大,很多文章在介绍saga模式的时候都会提到这个。但其实这种模式和原来的saga论文关系不大,和各家实现的saga模式关系不大,所以这里没有专门讨论这种模式。

saga设计dtm。
dtm支持TCC和saga模式,它们有不同的特点,各自适应不同的业务场景,相互补充。
image.png

上表对比了TCC和SAGA这两种交易模式。

TCC的定位是一致性要求高的短事务。一致性要求高的事务一般都是短事务(一个事务长期未完成,在用户眼里一致性比较差,一般不需要采用TCC的高一致性设计),所以TCC的事务分支排放在AP端(即程序代码),用户灵活调用。这样用户就可以根据每个分支的结果灵活判断和执行。

SAGA的定位是长事务/短事务,一致性要求低。对于预订机票这样的场景,持续时间长,可能持续几分钟到一两天。需要将整个事务安排保存到服务器中,避免因升级、故障等原因导致事务安排信息丢失。

状态机提供的灵活性对于客户端安排的TCC来说是不必要的,但是对于保存在服务器端的saga来说是有意义的。当我第一次设计saga时,我做了详细的权衡选择。这种状态机很难上手,用户很容易气馁。我找了一些用户做需求研究,总结出来的核心需求是:

子事务并发执行,减少延迟。比如旅游订票业务预订往返机票,因为订票可能需要很长时间才能确认,等机票订好了再订返程机票,很容易导致订不到。
有些操作不能回滚,需要放在可回滚子事务之后,保证一旦实施,最终会成功。
在这两个核心需求下,dtm的saga最终没有使用状态机,而是支持子事务的并发执行和指定子事务之间的顺序关系。

以实际问题为例,说明dtm中saga的用法。

对于订票业务,子事务的执行结果不是立即返还的,通常是第三方在订票后一段时间内通知结果。在这种情况下,dtm的saga提供了很好的支持,支持子事务返回的结果和指定的重试时间间隔。订票的子事务可以在自己的逻辑中。如果没有下单,就下单;如果已经下单,此时是重试请求,可以去第三方查询结果,最后返回成功/失败/进行。

解决问题实例
我们用一个真实的用户案例来讲解dtmsaga的最佳实践。

问题场景:一个用户旅行的应用,收到一个用户旅行计划,需要预订去三亚的机票,三亚的酒店,返程的机票。

要求:

两张机票和酒店要么预订成功,要么回滚(酒店和航空公司提供了相关的回滚界面)
预订机票和酒店并发,避免串行,因为某个预订的最终确认时间较晚,导致其他预订错过时间。
确认预定结果的时间可能从1分钟到1天不等。
以上要求,正是saga事务模式需要解决的问题,我们来看看dtm是如何解决的(以Go语言为例)。

首先,我们根据要求1创建一个saga事务,包括三个分支,即预订三亚机票、酒店和返程机票。

saga := dtmcli.NewSaga(DtmServer, gid).
     Add(Busi+"/BookTicket", Busi+"/BookTicketRevert", bookTicketInfo1).
     Add(Busi+"/BookHotel", Busi+"/BookHotelRevert", bookHotelInfo2).
     Add(Busi+"/BookTicket", Busi+"/BookTicketRevert", bookTicketBackInfo3)

然后根据要求2,让saga并发执行(默认是顺序执行)

saga.EnableConcurrent()
最后,我们处理了3中的预定结果的确认时间,这不是一个即时响应的问题。因为不是即时响应,所以不能让预定操作等待第三方的结果,而是在提交预定请求后立即返回状态。如果我们的分支事务没有完成,dtm将重试我们的分支,我们将重试间隔指定为1分钟。

saga.SetOptions(&dtmcli.TransOptions{RetryInterval: 60})
 saga.Submit()
// ........
func bookTicket() string {
   order := loadOrder()
   if order == nil { // 尚未下单,进行第三方下单操作
       order = submitTicketOrder()
       order.save()
   }
   order.Query() // 查询第三方订单状态
   return order.Status // 成功-SUCCESS 失败-FAILURE 进行中-ONGOING
}

高级用法
在实际应用中,还遇到了一些业务场景,需要一些额外的技巧来处理。

支持重试和回滚。
dtm要求业务明确返回以下值:

SUCCESS表示分支成功,可以进行下一步。
FAILURE表示分支失败,整体事务失败,需要回滚。
ONGOING表示,后续按正常间隔进行重试。
其他表示系统问题,后续按指数退避算法进行重试。
部分第三方操作无法回滚
比如一个订单中的发货,一旦给出发货指令,涉及到线下相关操作,很难直接回滚。如何处理涉及这种情况的saga?

我们把一个事务中的操作分为可回滚操作和不可回滚操作。然后把可回滚操作放在前面,把不可回滚操作放在后面,就可以解决这样的问题了。

saga := dtmcli.NewSaga(DtmServer, dtmcli.MustGenGid(DtmServer)).
      Add(Busi+"/CanRollback1", Busi+"/CanRollback1Revert", req).
      Add(Busi+"/CanRollback2", Busi+"/CanRollback2Revert", req).
      Add(Busi+"/UnRollback1", Busi+"/UnRollback1NoRevert", req).
      EnableConcurrent().
      AddBranchOrder(2, []int{0, 1}) // 指定step 2,需要在0,1完成后执行

超时回滚
saga属于长事务,因此持续的时间跨度很大,可能是100ms到1天,因此saga没有默认的超时时间。

dtm支持saga事务单独指定超时时间,到了超时时间,全局事务就会回滚。

saga.SetOptions(&dtmcli.TransOptions{TimeoutToFail: 1800})

在saga事务中,设置超时时间一定要注意,这类事务里不能够包含无法回滚的事务分支,否则超时回滚这类的分支会有问题。

其他分支的结果作为输入
如果少数实际业务不仅需要知道某些业务分支是否成功实施,还需要获得成功的详细结果数据,那么dtm如何处理这样的需求呢?比如B分支需要A分支实施成功返回的详细数据。

dtm的建议是在ServiceA提供另一个接口,让B获取相关数据。虽然这个方案效率略低,但是很容易理解,已经维护了,开发工作量也不会太大。

PS:请注意一个小细节,尽量在你的事务之外进行网络请求,避免事务时间跨度变长,造成并发问题。

小结
本文总结了与saga相关的理论知识和设计原则,并对比了saga的不同实现及其优缺点。最后,实际问题案例详细说明dtmsaga事务的使用。

dtm是一站式分布式事务解决方案,支持SAGA、TCC、XA等事务模式,支持Go、Java、Python、PHP、C#、Node等语言SDK。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,大概有三种登录方式:
9068 0
InnoDB 事务加锁分析
以Mysql Innodb为例,介绍了事务的4种隔离级别以及不同的隔离级别是如何实现
675 0
Fescar&Seata分布式事务实现原理解析探秘
前言 fescar发布已有时日,分布式事务一直是业界备受关注的领域,fescar发布一个月左右便受到了近5000个star足以说明其热度。当然,在fescar出来之前,已经有比较成熟的分布式事务的解决方案开源了,比较典型的方案如LCN(https://github.com/codingapi/tx-lcn)的2pc型无侵入事务,目前lcn已发展到5.0,已支持和fescar事务模型类似的TCX型事务。
1979 0
PostgreSQL的事务隔离分析
隔离级别(Isolation levels) 有四种隔离级别: 可序列化(Serializable) 可重复读(Repeatable reads) 提交读(Read committed) 未提交读(Read uncommitted) ...
2381 0
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
19698 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
17986 0
ABAP代码分析工具 - 事务码SQF
ABAP代码分析工具 - 事务码SQF
26 0
蚂蚁金服分布式事务实践解析 | SOFAChannel#12 直播整理
本文根据 SOFAChannel#12 直播分享整理,主题:蚂蚁金服分布式事务实践解析,也就是分布式事务 Seata 在蚂蚁金服内部的实践。
817 0
RocksDB TransactionDB事务实现分析
## 基本概念 #### 1. LSN (log sequence number) RocksDB中的每一条记录(KeyValue)都有一个LogSequenceNumber(后面统称lsn),从最初的0开始,每次写入加1。该值为逻辑量,区别于InnoDB的lsn为redo log物理写入字节量。 这个lsn在RocksDB内部的memtable中是`单调递增`的,在WriteA
3673 0
+关注
IT枫斗者
公众号:IT枫斗者,根据关键字领取福利资料,我2013年毕业,至今工作将近9年了,目前在上海工作,也是公司的面试官,曾就职过科大讯飞、美团网、平安等公司,擅长做面试辅导、定制化包装简历、项目包装等,个人微信:itfdz666,欢迎交流疑惑!
114
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
OceanBase 入门到实战教程
立即下载
阿里云图数据库GDB,加速开启“图智”未来.ppt
立即下载
实时数仓Hologres技术实战一本通2.0版(下)
立即下载