在前面《从银行转帐失败到分布式事务:总结与思考》一文中,已经总结了分布式事务的各类解决方法,以及本身的一点思考。本文算是对上文的一个补充:进一步的思考,主要是这些解决方法在工程上的具体实施。若是后面在工做学习中有新的想法,也会持续更新到本文。html
本文地址:http://www.cnblogs.com/xybaby/p/7756163.html数据库
在前文中,已经简要介绍了2PC、TCC、基于异步消息、1PC这几种分布式事务解决办法,这里在补充一种:best effort。segmentfault
best effort即尽最大努力交付,主要用于在这样一种场景:不一样的服务平台之间的事务性保证。好比咱们在电商购物,使用支付宝支付;又好比玩网游的时候,经过App Store充值。拿购物为例,电商平台与支付平台是相互独立的,隶属于不一样的公司,即便是同一个公司也极可能是独立的部门。所以,这两个平台是不可能使用同一套分布式事务框架的,2PC不行,tcc也不行,异步消息也不行。安全
其实在上面电商平台与支付平台的例子中,涉及到多重事务性:服务器
电商平台与支付平台之间的事务性:电商的下单操做与支付平台扣款的原子性,不能说支付平台扣了用户的钱,但电商平台不发货;或者说,电商平台先发了货,支付平台没有扣用户的钱;网络
电商平台内部的事务性:好比订单与优惠券、红包等;架构
支付平台内部的事务性:好比用户帐户、商户帐户等;并发
不论是由于技术缘由,仍是说安全策略,支付平台只会提供给电商平台一些Http接口,即开放支付服务。电商平台在发出一笔支付请求后,是不大可能马上得到支付是成功仍是失败的确切消息,更多的时候应该是请求已被接受,处理中。这个时候支付平台已经将该请求持久化,保证必定会处理这个请求。当支付平台处理完这个支付请求以后,怎么将结果通知给电商平台呢,要么是电商平台定时轮训,要么是电商平台在初始支付请求的时候携带一个callback,提供给支付平台回调。在这篇文章中提到,支付宝采用的是回调的形式:oracle
“作过支付宝交易接口的同窗都知道,咱们通常会在支付宝的回调页面和接口里,解密参数,而后调用系统中更新交易状态相关的服务,将订单更新为付款成功。同时,只有当咱们回调页面中输出了success字样或者标识业务处理成功相应状态码时,支付宝才会中止回调请求。不然,支付宝会每间隔一段时间后,再向客户方发起回调请求,直到输出成功标识为止。”框架
这个例子,绘制成流程图就是这样样子的:
再想一想上文提到的银行转帐的例子,极可能也是采用best effort这种模式,银行之间确定是相互独立的。首先是本地银行先扣款,而后通知另一个银行加款,但为何对方加款失败,没有通知到本地银行,就不清楚了
在这里主要经过如下几个维度来对比分析:
注意,上面提到的回滚和补偿是一个意思,“回滚”不局限于DB里面的术语,而是指通用的对某个操做的逆反操做
2PC的强一致性依赖于数据库,而TCC的强一致性依赖于应用层的Commit与cancel。异步消息,1PC,best effort都只保证最终一致性(且最终一致性还可能依赖于人工介入,是否应该算弱一致性?)
2PC须要对整个资源加锁,所以不适用于高并发的分布式场景;而tcc只对须要的资源进行加锁,加锁的粒度小,且try commit Cancel都是本地短事务,所以能在保证强一致性的同时最大化提升系统可用性。而异步消息,1PC,best effort都是先提交一部分事务,无需加锁。
2PC是有数据库来保证回滚,而TCC是应用层实现回滚:为每个try操做提供一个对应的cancel操做。而异步消息,1PC适用于理论上必定会成功的场景,难以回滚。best effort这种模式,须要服务的调用者实现完整的一个事务操做用于回滚,好比支付失败的状况。数据库的回滚较简单,而应用层的回滚较为困难,更重要的是,回滚也须要做为一个事务进行,部分回滚失败的状况最可怕。
至于子事务的串行、并行,在其余文章中并无看见过相关讨论,但确定是实践的时候必需要考虑的问题。即一个分布式事务确定是由多个分支事务组成,那么多个分支事务是并发执行,仍是串行执行呢?特别对于2PC,TCC这些分为多个阶段的解决方案,每一个阶段是并发,仍是串行呢
首先,对于异步消息,best effort,确定都是串行的,其中一个分支事务完成以后,再去作另外一个分支事务。
但对于2PC,TCC,理论上看起来是并行的,但工程实践中有能够串行。以2PC为例
2PC从介绍的文章来看,多属于并行:即协调者同时让参与者prepare,而后在第二阶段同时通知参与者commit或者abort,下面两个图说明了这个并行的过程。
上面分别是两阶段提交协议成功commit与失败abort的状况,能够看出在prepare阶段,多个参与者是并行的。
而2PC的串行模式,就是说,先通知一个参与者准备,成功的话再通知另外一个参与者准备,即准备阶段是串行的。下图来自支付宝:
注意 上面的图示,第二阶段(commit 或者 abort)也画成串行的,这里应该是能够并行的。
那么串行、并行的区别在于哪里呢
(1)并行效率高,整个事务的耗时更少;
(2)而串行在prepare阶段失败的状况下,只需部分回滚;
在工程实践中为何会采用串行这种方式呢,这是另一个重要的优化: “最末参与者优化”(Last Participant Optimization,术语来自支付宝),即容许两阶段提交协议中有一个参与者不实现“准备”操做,在其他参与者都prepare ok的状况下,直接提交本身的分式事务。
网络上关于LPO的介绍并很少,在oracle官网Logging Last Resource Transaction Optimization中有以下介绍:
The LLR resource uses a local transaction for its transaction work. The WebLogic Server transaction manager prepares all other resources in the transaction and then determines the commit decision for the global transaction based on the outcome of the LLR resource’s local transaction.
最末参与者优化的原理以下图所示:
本质上,LPO是将最后一个参与者的准备操做与提交/放弃操做合并成一个提交操做,这样提升了分布式事务的执行效率。也能够看到,要使用LPO,在prepare阶段必定是串行的。
对于TCC,流程也是很是相似2PC,即在Try阶段,也可使用LPO,在《说说分布式事务 》一文中,给出了一个实例的详细流程图。
在一些业务场景,是无需单独的协调者,即事务的发起者同时是组成事务的分支事务。好比支付宝的例子,业务服务和帐户服务组成一个分布式事务,在业务服务上发起事务请求,所以没有单独的协调者服务器,使用LPO也比较适合。
前面已经介绍过TCC的三个阶段,Try负责预留资源,Commit提交预留的资源,Cancel“回滚”预留的资源。那么某一个分支事务的Try操做是否能够直接作Commit所作的事情呢,即Try操做直接提交分支事务。在这种状况下,若是全部分支事务的Try阶段都返回OK,那么该分支事务的Commit就什么都不用作,若是须要Cancel,那么就实现回滚。
固然,我看到的更多形式,好比支付宝的XTS,都只是冻结资源:加额外的字段,代表有多少数量的资源处于特殊状态。
咱们以一个扣款操做做为分支事务,好比要从帐户A扣除100元。若是Try阶段直接执行事务,那么就从A的帐户上真正扣除了,而Cancel阶段则加上100,看起来很容易;若是Try阶段只是冻结,那么就会复杂一些,一个可行的方案增长forzen字段的值,同时扣除帐户。
但若是考虑加款操做做为分支事务,Try阶段直接执行事务的话,极可能出现cancel阶段钱不够的状况(假设资金不能为负)
所以,我的以为,TCC框架是不用关心具体形式的,业务只需向框架注册这三个操做就好了,具体怎么操做,彻底取决于业务,能知足业务的需求就行。
在龙果学院退出的课程《微服务架构的分布式事务解决方案》中,综合运用了各类分布式事务解决方案,以下如所示:
在上图中,使用了三种分布式事务解决办法:
(1)基于可靠消息的最终一致性方案(异步确保型),这个使用比较广,适用于分支事务大几率成功的状况;
上图中使用于:对应支付系统会计异步记帐业务,银行通知结果信息存储与驱动订单处理
(2)TCC事务补偿性方案,使用在同时须要保证一致性与高性能的场景
对应上图中支付系统的订单帐户操做:订单处理,资金帐户处理,积分帐户处理
(3)best effort,最大努力通知型方案,适用于跨平台之间的事务原子性保证
对应上图中支付系统的商户业务通知场景