简介:大家好,我是枫哥,一线互联网的IT民工、资深面试官、Java跳蚤网课堂创始人。拥有多年一线研发经验,曾就职过科大讯飞、美团网、平安等公司。在上海有自己小伙伴组建的副业团队,目前业余时间专注Java技术分享,春招/秋招/社招/跳槽,一对一学习辅助,项目接活开发。
扫码左侧二维码,加入群聊,一起学习,一起进步!
欢迎关注 收藏 留言
:文末送福利
目录
saga的理论来源
回滚与重试
并发执行
saga的实现分类
状态机实现
非状态机实现
saga设计dtm。
解决问题实例
高级用法
支持重试和回滚。
部分第三方 *** 作无法回滚
超时回滚
其他分支的结果作为输入
小结
saga是分布式事务领域非常重要的事务模式,特别适合解决旅游订票等长期事务。本文将深入分析saga事务的设计原理和解决订票问题的最佳实践。
saga的理论来源这种事务模式saga最早来自这篇论文。http://www.amundsen.com/downloads/sagas.pdfhttp://www.amundsen.com/downloads/sagas.pdf在这篇论文中,作者提出将一个长事务分为多个子事务,每个子事务都有正向 *** 作Ti和反向补偿 *** 作Ci。
如果Ti依次成功完成所有子事务,则全局事务完成。
如果子事务Ti失败,将调用Ci、Ci-1、Ci-2..进行补偿。
论文阐述了上述部分的基本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模式,它们有不同的特点,各自适应不同的业务场景,相互补充。
上表对比了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。
感谢大家,坚持看完,既然选择了这条路,那就一起加油,一起学习!如果需要学习资源,实战面试资料,项目资源。关注公众号:IT枫斗者,根据关键字领取对应的资料福利!咨询解决问题,公众号私聊枫哥,备注来意。
回复:java全套学习资源
回复:面试资料
回复:枫哥简历
回复:程序员表白神器
(从此告别程序员单身狗!)
回复:程序员兼职网站
回复:枫哥666
( 获取66套项目实战资料,大厂面试视频)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)