文档参考:书名:《企业it架构转型之道》-钟华
前文如下:
柔性事务在阿里巴巴内部的几种实现
基于上次柔性事务实现分布式事务的思路以及从多年对互联网业务场景特性的深度剖析,从阿里巴巴内部共发展和演变出三套成熟的分布式事务解决方案,下面分别介绍。
(1)消息分布式事务
在淘宝平台中,被广泛用来解决分布式事务场景的方案就是基于消息分布式事务,通过MQ事务消息功能特性达到分布式事务的最终一致。
实现原理及流程
步骤的①②是在MQ发送方(即整个分布式事务的发起方)执行第一个本地事务前,会向MQ服务端发送一条消息,但这条消息不同于MQ上的普通消息,而是一条事务消息(事务消息功能是阿里巴巴MQ平台特有的一个功能特性),事务消息在MQ的服务端处于一个特殊的状态,此时该消息已经保存到MQ服务端,但MQ订阅方是无法感知到该消息,更无法对该消息进行消费,否则就可能出现第一个本地事务并没有执行成功,而后面一个本地事务执行的业务不一致问题。
完成了事务消息的发送后,步骤③才正在开始执行对第一个数据库进行单机事务的操作。 此时本地事务执行的情况决定了后面几个步骤的各自目的:
a)当本地事务执行成功后,会执行步骤④将原本保存在MQ服务端的事务消息的状态更新为正常的消息状态。
b)当本地事务执行时,如果因为某些原因(网络或当前运行机器宕机)造成程序没有及时给MQ服务端相应的反馈,则之前发送到MQ服务端的事务消息会一直保存在MQ服务端,为了保证事务继续执行,MQ服务端提供了对服务器上保存在事务消息堆栈中的事务消息进行定时扫描,如果发现一段有事务消息在该堆栈的保存时间超过了一段时间(比如5分钟),此时MQ服务端会执行步骤⑤,发起一个请求发送到具有跟之前MQ发送发具有同样生产者ID的MQ发送方(与前一发送方具有同一应用代码)其中的一个实例上(因为有可能之前运行的那个实例已经宕机了),该请求的目的是让MQ发送发去检查之前执行的本地事务到底是否执行成功还是失败。
c)步骤⑥在对数据库进行了之前本地事务执行结果的确认后,如果发现本地事务根本没有执行,则给MQ服务端返回结果,告知MQ服务端可扔弃该事务消息;如果检查发现之前执行的本地事务实际上已经成功执行了,只是因为各种原因没能及时到MQ服务端更新事务消息的状态,此时只需更新服务端上事务消息的状态为正常状态即可。所以在前面这部分步骤中,核心是让第一个本地事务的执行和MQ服务端的消息能否被投递和消费同时成功或者同时失败,而不会出现本地事务并没有被成功执行,但消息已经被消息的消费者消息,进行了下一个本地事务的执行,在某种程度上是保证了本地事务和消息发送的事务性,所以我们称为该类消息为事务消息。
步骤⑧则是在消息订阅方获取到由事务消息置为正常状态的消息后,通过消息里的事务和业务信息执行第二个本地事务的执行。如果第二个本地事务执行成功,则最终实现了两个不同数据库上的事务同时成功;如果第二个本地事务执行失败,则还可通过消息的方式通知MQ发送发,对第一个本地事务进行业务的回滚操作。
总体来看,通过消息进行事务异步的方式,保证了前后两个数据库事务同时执行成功或失败,保持了事务的一致性,同时因为避免了传统两阶段提交事务方式对数据长时间的资源锁定,所以数据库整体的吞吐率和性能大大超过传统的分布式事务方式。
从本质上来说,对比柔性事务解决分布式事务的思路,消息服务在其中扮演了事务日志的职能,对全局事务有一个统一的记录和调度能力;事务的参与者通过对消息订阅关系建立了事务间的关联。在采用消息服务实现分布式事务的场景如果出现异常时,一般会采用正向补偿的方式,即不会像传统事务方式出现异常时依次进行回滚,会通过消息的不断重试或人工干预的方式让该事务链路继续朝前执行,而避免出现事务回滚。
典型应用场景。在淘宝平台中,使用消息服务实现分布式事务的场景众多,其中淘宝的订单交易则是对这一分布式事务方式体现最为典型的一个场景。
图中分别示意了在淘宝下单和付款两个操作时几个主要业务步骤的执行示意,其中在下单这个分布式事务操作中包含了库存预减、创建交易订单、创建支付订单几个主要操作,核心就是通过MQ消息服务的方式实现了这几个操作的事务最终一致性。在付款事务中,对扣款、创建扣款流水、实减库存、修改订单状态等进行了事务操作,其中也都是有MQ服务实现了整个分布式事务的事务一致性。
在订单创建或付款出现异常,比如实减库存失败、付款超时时,同样也会通过消息服务的方式,通知相关的服务进行订单状态的修改、支付宝中支付订单状态的更新及退款操作、预减库存回撤等相关操作,所有的这些操作可能由不同的服务完成相应操作,但整体保持事务性。整个过程如图所示。
总之,采用消息服务实现的分布式事务很好地实现了应用服务化后业务处理流程的异步化,大大提升了整个业务处理的吞吐率和响应时间。但你会发现采用消息事务的方式,在两个事务间实现分布式事务时,可以很好地满足事务最终一致性以及事务的回滚,但如果一个事务上下文中超过两个事务操作后,因为事务的回滚逻辑变得非常复杂而不可控,所以在这样的场景下只能进行正向的事务补偿,在某些业务场景下会给带给客户不同的体验。
(2)支付宝XTS框架
支付宝的XTS分布式事务框架是基于BASE的思想实现的一套类似两阶段提交的分布式事务方案,用来保障在分布式环境下高可用性、高可靠性的同时兼顾数据一致性的要求。与上面介绍的基于消息实现的分布式事务仅支持正向补偿,XTS可同时支持正向和反向补偿。
XTS是TCC(Try/Confirm/Cancel)型事务(如图所示),属于典型的补偿型事务。
❑Try阶段主要是对业务系统做检测及资源预留。
❑Confirm阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行Confirm阶段时,默认Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
❑Cancel阶段主要是在业务执行错误需要回滚的状态下,执行业务取消,预留资源释放。
总结。本质上支付宝的XTS给开发人员提供了一个实现分布式事务的事务框架,主要负责事务日志的记录,事务的参与者需要实现XTS提供的接口,以实现XTS框架对事务参与者的事务协调和控制。通过Try实现业务的软隔离,避免了耗时的真正数据锁,从而在整体上相比于传统的分布式事务有更好的性能和处理吞吐率。但总体来说,因为需要开发人员实现事务的补偿机制,对于开发人员的心智负担过于沉重。所以只能依赖TCC服务器的失败重试机制,如果失败重试机制不能处理,只能人肉去处理(建议对重试次数需要限定,因为同时进行失败重试和人肉的话,如果失败重试和人肉操作都在操作同一条数据,还需要考虑这种竞争的场景)。 为了真正将开发人员从自己实现事务补偿的重心智的负担中解脱出来,才有了接下来给大家介绍的阿里巴巴新一代分布式事务平台TXC。
(3)TXC架构概览
在通过对业界现有分布式事务平台的深入研究,围绕着开发人员使用这些平台的各种优点和弊端,研发设计出了新一代分布式事务平台TXC,其功能如下:
❑TXC完全能满足之前分布式事务平台所提供的对于事务服务高可用和事务最终一致性的基本业务要求。
❑标准模式下无需开发人员自行进行事务回滚或补偿的代码,平台支持自动按事务中事务操作的顺序依次回滚和补偿。
❑易用性是TXC的主要目标,在保证事务完整性的前提下,标准模式可不修改应用的代码,同时也提供之前平台中所提供的事务重试以及自定义事务模式。
TXC架构概览。 TXC同样也是基于两阶段提交的理论实现的分布式事务框架,全面支持分布式数据库事务、多库事务、消息事务、服务链路调用事务及其各种组合场景下的事务,架构图如图所示。
架构图中,Client是与Server进行交互的客户端,其中某一类客户端称为事务发起者,事务的创建提交必须由事务发起者发起,在代码层界定事务边界,即整个事务上下文中对于各事务操作按业务需求进行组装,另一类客户端则是在事务中调用的服务提供者。在出现业务异常时,会由TXC的客户端发起事务的回滚。
TXC Server扮演了事务协调者的角色,负责对整个事务上下文的日志的记录以及在事务处理过程中全局的协调和控制。具体包含对事务以及分支事务的注册,对事务的提交和回滚进行统一的管控。同时平台对异常情况进行捕捉并发起对应的事务回滚或提交操作,参见表6-1。
架构图中的RM(Resource Manager)为资源管理器,一般管理多个TXC数据源,负责在TXC客户端进行数据源访问时,与TXC服务器进行事务的注册和状态更新。 TXC数据源是在原有的数据源基础上做了一层较薄的封装,因为TXC需要拦截和捕捉到所有TXC客户端对于数据库进行的数据修改,从而为事务的自动回滚提供数据的原始值。
相比于传统的两阶段提交方式,最大的区别在于XA在准备阶段是没有提交本地事务的,而TXC则是立即执行并可见,在隔离性级别上实现的是读未提交(read uncommitted),所以避免了在分布式事务中对于数据的长时间锁占用。也就是说,TXC在允许数据脏读的业务场景中,能充分发挥性能上的优势。比如商品在大促秒杀场景下,允许商品的库存在事务没有提交前给前端应用提供查询,只不过在最后订单扣减库存时进行控制,避免商品超卖现场的发生。如果业务场景不允许数据的脏读,TXC平台也支持select for update以及提供@hint的功能临时提升事务的隔离级别。
同时,TXC服务器端会记录当前处理事务对数据库中进行了修改数据的信息(行信息),当有其他事务也要对这些数据进行修改操作时,TXC服务端会协调两个事务间的执行,避免在第一个事务没有提交前,同样的数据会被另一个事务对该数据进行修改。从本质来说,将原来传统事务场景下,由数据库提供的锁机制提升到了TXC服务端进行了实现,这样相比于数据库锁的实现成本更加轻量,加上TXC本身服务能力的扩展能力,最终在同样实现事务隔离性的前提下,大大提升了整体的数据库处理吞吐率。
TXC两阶段提交实现。TXC也是基于两阶段提交理论实现,由TXC服务器负责整体的事务协调和管理,由部署在TXC客户端上的资源管理器组件实现各客户端与TXC服务器的事务注册、状态更新、提交等操作,一个标准的两阶段提交事务处理时序图如图所示。
1)在图中,实现了两个事务通过TXC实现整体事务性的流程,第一个事务是对某个数据库的数据修改操作;第二个事务是调用远程服务的RPC调用,在该RPC服务中也会实现对另外一个数据库进行数据的修改。
2)步骤1)是事务发起者在执行这两个事务前,首先会在TXC服务器上对该事务进行注册。
3)步骤2)是事务发起者首先发送一条SQL请求进行数据修改操作,该SQL请求被TXC感知到后,会向TXC服务器对该分支事务进行注册(步骤3))。 4)步骤4)则是对在实际的数据库上执行SQL操作,同时进行undo和Redo日志的生成,在执行了这些一系列的数据库本地事务操作并提交后,会向TXC服务器更新该分支事务的状态(步骤5)。
5)步骤6是从事务发起者在完成了第一个数据库本地事务的操作后,进行远程的RPC调用,同样,在RPC服务调用过程中,一旦出现对数据库修改的操作,则会再次注册该分支事务到TXC服务器上(步骤7)。
6)在步骤8中完成了如步骤4同样的数据库本地事务操作并提交后,更新该分支事务状态到TXC服务器上。
7)最后当两个事务都完成并没有异常出现的情况下,事务发起者会发起整体事务更新的请求,此时会依次对之前的两个分支事务进行状态的提交;当在整个事务过程中出现代码异常或网络异常时,则会依次对两个事务中进行的数据修改进行自动的回滚操作,将数据恢复到该事务执行前的状态,从而实现了整个事务操作的业务一致性。
TXC如何实现事务自动回滚。TXC平台提供了对事务的自动回滚,使得开发人员不会像前两种方案中要实现业务的正向补偿或回滚,大大降低了开发人员对业务逻辑深度理解和额外开发方面的要求。这也是TXC相比于今天业界其他分布式事务框架中最大的一个特点,接下来通过TXC实现事务自动回滚的流程介绍一下这一关键功能的实现原理,如图所示。
1)用户在向TXC服务器发起事务请求后,进入到数据库的操作时,会对该分支事务在TXC服务器上进行注册。当资源管理器捕捉到SQL的请求后,会对SQL语句进行SQL解析,如果是执行Insert/Delete/Update的SQL操作,则会针对该SQL语句构造出对应的SQL查询语句,将当前SQL请求要修改的数据先从数据库中获取,以Undo日志的方式保存起来,用于将来回滚。
2)进行实际的SQL语句的执行,在SQL执行完毕以后,会再次通过查询方式获取到修改后的数据,并保存为Redo日志,用于业务回滚前脏数据的校验。
3)当SQL的执行和Undo/Redo日志作为一个本地事务提交给数据库的同时,也会更新分支事务状态。当整个事务成功提交后,则会删除Undo/Redo日志。
当出现事务回滚时,会按以下顺序进行数据的恢复和操作。首先对比当前数据库中数据值与之前保存的Redo日志中被修改的值是否一致,如果一致则根据Undo日志生成回滚用的Undo SQL并执行,恢复数据到执行事务前的状态;如果当前数据库中的数据与Redo日志中的值不一致,则说明是该分布式事务在第一阶段修改了数据后,又被其他线程(可能是通过非TXC事务控制的数据访问渠道)修改了该数据,这样就不能再继续进行数据的自动回滚,否则会出现业务不一致的情况,回滚会抛出异常,由TXC Server发出告警,引入人工干预。
关于柔性事务的总结
从电商领域到电信、金融领域,今天在我们面对的众多业务平台建设时,发现其中绝大部分场景下,我们都不需要用两阶段提交这样低效的方式来解决分布式事务问题。上面描述的几个最终一致性方案,都很好地在保证业务一致性的前提下,展现出极高的系统吞吐能力。为了充分发挥柔性事务框架性能的优势并实现业务的最终一致,需要采纳以下配合方案:
❑应用程序一定要做幂等实现,特别是对数据库进行数据修改操作时。
❑远程模块之间用异步消息来驱动,异步消息还可以起到检查点的作用。两阶段提交的方案可以保证最强的ACID要求,开发者因此不需要仔细考虑自己的应用到底可以接受什么级别的ACID;同时,两阶段提交的方案开发简单,开发者只需要指定事务的边界即可。而最终一致性方案往往意味着更高的事务处理性能及处理吞吐率,但有些实现方案需要开发人员更全面地了解前端业务以实现事务的正向补偿或反向回滚,也会付出有损事务隔离性的代价。所以一定要在业务上精确分析自己的ACID需求,寻找性能与ACID的折中点,采取最合适的方案。