本文主要是讲述分布式事务的理论及常用的技术方案,主要源自各类学习和工作总结,如有不妥之处,还望指正。分布式事务的其他基础请自行查阅资料。
一、分布式事务产生的原因
分布式事务的产生,源自互联网、电商等的发展,当同一个系统不同模块不同业务的数据在一个存储设备里,随着业务的发展,系统逐渐满足不了业务的发展时,常用的手段就是“拆”,拆的手段有垂直拆分和水平拆分,针对业务模块和数据库存储,都可以进行垂直拆分和水平拆分。拆分后就会存在不同的业务使用自己的数据库进行存储,这就会导致一个操作需要进行跨数据库操作。这就是分布式事务产生的最基本的原因所在。而我们知道,只要是事务,必须要满足事务的四性(ACID),为了使事务的四性得到满足,业内使用了多种技术手段,但各种技术手段都有其优点和缺点。
事务的四性(ACID):Automicity(原子性)、Consistency(一致性)、Isolation(隔离性)、Durability(持久性)。
比如:电商的下单,里面包含写订单表、扣减商品库存、写财务结算,订单信息、商品库存、财务模块按业务已经拆分到不同的模块,各自有属于自己的数据库,这个时候就是一个典型的分布式事务场景。
二、理论基础
此处主要说2个理论基础,一个是分布式的CAP定理,一个是BASE理论。
CAP:指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)这三个要素最多只能同时实现两点,不可能三者兼顾。在分布式场景中,由于网络硬件等客观因素,网络之间的通信可能会存在中断、丢包等情况,所以分区容错性(Partition tolerance)是我们分布式场景中必须要满足的,三要素中就只能有有这2种组合:CP和AP。
AP:AP模型强调的是系统的可用性,在做系统设计时,需要优先考虑可用性;
CP:CP模型强调的是系统的一致性,在做系统设计时,需要优先考虑一致性;
基于CAP定理的AP模型和CP模型,又演化出了BASE理论。BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)的简写。核心是既然没办法做到强一致性,但每一个应用都可根据自身的业务特点采用适当的方式来达到最终一致性。
Basically Available(基本可用):指系统出现不可预知的故障时,允许损失部分可用性,但这绝不等价于不可用。比如:系统某功能的正常响应时间是0.1秒,但由于系统出现异常(机房断电、光纤挖断等)系统功能的响应时间升到1-2秒;再比如电商的大促或秒杀,为了保证系统的稳定性,当用户流量超过了系统阈值,可把部分用户引流到一个降级页面。
Soft state(软状态):与硬状态相对。系统中的数据存在中间状态,并认为该中间状态不影响系统的整体可用性,即表示数据副本之间的同步有延迟。
Eventually consistent(最终一致性):系统中的所有数据副本,在经过一段时间后,所有数据的状态都能达到一个最终的一致的状态。
三、刚性分布式事务
刚性分布式事务的特点是:数据的状态强调的是强一致性,系统能支持的并发低,事务执行的时间都比较短,属于短事务,所有数据在事务内同步执行。刚性分布式事务遵循XA协议,通过实现XA的接口来实现分布式事务。XA规范由AP(Application Program)、RM(Resource Manager)、TM(Transaction Manager)组成。
AP:定义事务的开始和结束,并访问事务内的资源;
RM:通常指的就是数据库资源;
TM:负责管理事务,分配事务的唯一标识、监控事务的执行情况、并负责事务的提交、回滚等操作;
下面列出一些常见的实现XA协议的分布式事务方法。
两阶段提交(2PC):
也是XA的标准实现,分为准备阶段和提交阶段,时序图如下:
AP向TM发起commit请求,TM向RM发送prepare,当TM收到所有的RM返回的ok消息后,TM再向所有的RM发送commit指令,待收到所有的RM返回的ok消息后,commit才算完成。在prepare阶段,当有一个RM返回失败的时候,则不能进行第二步的commit操作,TM释放所有占用的资源;在commit阶段,当有一个RM返回失败时,则TM都要协调commit进行补偿,保证所有提交到RM的commit请求,要么都成功、要么都失败。
2PC的缺点:
1、同步阻塞:所有参与事务的资源都处于阻塞状态;
2、TM瓶颈:当TM故障时,所有的参与者都将被锁定,资源得不到释放;
3、RM资源锁定时间过长;
4、全局锁定(隔离级别串行化),不适合长事务,并发低;
基于2PC的缺点,又提出三阶段(3PC)提交。
三阶段(3PC)提交:
三阶段(3PC)提交分为CanCommit、PreCommit和DoCommit三个阶段,时序图如下:
CanCommit:TM向所有RM发出CanCommit指令,RM收到指令后,判断可否提交事务,如果可以返回ok,否则返回no;
PreCommit:当TM收到所有RM都返回CanCommit的结果为ok时,TM向所有RM发出PreCommit;当有一个RM返回no或超时,导致TM没收到反馈则事务中断,TM向所有RM发出abort终止事务,TM收到abort后终止事务,释放资源。如果RM没收到TM发出的abort或是超时,则RM也会中断自身的事务,释放资源;
DoCommit:TM收到所有RM都返回PreCommit的结果为ok时,TM向所有RM发出DoCommit,执行事务真正的提交,TM收到所有RM的DoCommit的执行结果为ok时,释放所占用的所有资源;当有一个RM返回no或超时,导致TM没收到反馈则事务中断,TM向所有RM发出abort终止事务,各个RM收到abort后利用CanCommit阶段的Undo信息执行回滚操作,释放占用的资源;但是,如果RM没收到TM发出的abort或是超时后,则RM会继续提交事务,这将导致数据的不一致。
三阶段相比两阶段,优点有:降低阻塞范围;TM瓶颈问题得到部分解决,即在第一二阶段时,当超时的时候RM会自动释放资源,不依赖TM。但进入第三阶段后,如果超时则不会释放资源,而会继续提交事务,这种情况下,将导致数据的不一致。
四、柔性分布式事务
柔性分布式事务是相对刚性分布式事务、是对强一致性的妥协,从而降低对数据库资源的锁定时间,提升系统的性能。柔性分布式事务适合于长事务、高并发,强调最终一致性的场合。常用的实现柔性分布式事务的方式有:TCC模型、Saga模型、基于消息队列的异步模型。
1、TCC(Try-Confirm-Cancel)模型
TCC是一个两阶段提交(2PC)的实现,每一个业务都需要实现Try-Confirm-Cancel三个接口,Try是尝试执行业务,完成所有业务执行前的检查;Confirm是真正执行业务,提交事务,释放资源;Cancel是业务失败的时候回滚业务操作,释放资源。
A账户向B账户转账100元为例,TCC执行步骤如下:
TCC模型的实现是分为2步操作完成一次事务操作,达到最终事务的一致性。
2、Saga模型
起源于1987年Hector Garcia-Molina和Kenneth Salem发表的论文《Sagas》,主要思想是把一个分布式事务拆分为多个本地事务,每一个本地事务都有相应的正常执行方法和异常补偿方法,当任意一个本地事务出错时,都可以通过调用相应的异常补偿方法恢复之前的事务或是继续执行未完成的事务,保证事务的最终一致性。
Saga事务由一系列的sub-transaction Tn及对应的补偿事务Cn组成。
在事务执行过程中,当有任何一个子事务Tj执行失败时,将触发事务补偿,事务的补偿有两种方式。一种是向后补偿,一种是向前补偿。向后补偿指子事务Tj失败后,将把已经执行的子事务Tj-1,Tj-2,……T2,T1的子事务通过执行各自对应的补偿事务Cj-1,Cj-2,……C2,C1(0<j<n)把整个事务回退到执行前的状态。
向前补偿指的是假设每一个子事务最终都会执行成功,则当子事务Tj失败后,将重试保证子事务Tj,Tj+1……Tn-1,Tn都执行成功,达到整个事务的最终一致性。
在实际应用中,并不是所有场景都适合用补偿操作,尤其是向后补偿,比如:发送邮件,这意味着事务补偿后,有可能回不到最初事务开始执行时的状态。
3、基于消息队列的异步模型
基于消息队列的异步模型指的是在核心业务执行完成后,同步的对外发出一个消息,供其他模块消费使用后执行各自的业务。例:电商的下单操作,假如其他的验证都已经完成,满足下单的情况下,给订单表写入订单信息后,系统生成了订单号,订单模块知道把带订单号的订单信息对外发出一个消息(MQ),下单操作就算完成。订单模块发出的MQ供后续的库存扣减,财务收款,仓库生产等业务使用,这里的库存扣减,财务收款和仓库生产属于不同的模块,各自的操作有自己的事务,这就实现了把一个大事务拆分为多个小事务,各自去执行,互不影响,提升系统性能的目的。目前基于消息队列的异步模型,其实现方式有多种,在这里我列出两种进行介绍,一种是业务方提供本地操作成功回查功能,一种是基于本地事务消息表。
1),业务方提供本地操作成功回查功能
整体的流程描述:本地事务执行前,把需要发送的mq消息先发送到MQ服务器上,但该消息属于不能投递的消息,需要有标识标明。当MQ发送方收到MQ服务器返回的收到发送的mq消息的确认后,执行本地事务,根据本地事务的执行结果再向MQ的服务器发送该消息的本地事务执行结果,告诉MQ服务器是把mq消息投递到消费方还是把mq消息回滚丢弃。当MQ服务端超时未收到MQ发送端对mq消息的处理通知(Commit OR Rollback)时,MQ服务端会向MQ发送方进行查询该mq消息的事务状态,以确定该mq消息的处理结果是投递还是丢弃。整体的操作流程图如下:
该方案需要业务方针对每一个事务提供一个回查接口给MQ服务端;同时,MQ服务端还需要有定时任务来检查未投递的消息,并计算消息是否已经超时,如果已经超时需要回查业务方提供的回查事务状态接口以便确认对该消息的处理;该方案对业务的侵入比较大,不利于方案的扩展。目前支持该模式的消息队列有Apache RocketMQ。
2),基于本地事务消息表
该方案指的是主业务数据表和待发送的消息存储表共存于同一个数据库,便于数据写入的时候由一个事务保证,数据写入成功后,事务即成功。此时,需要一个任务来执行对消息表里的消息进行发送到MQ,消息发送MQ成功后,删除消息表里的消息,达到异步处理的目的。比如:还是下单操作,下单完成后,需要通知库存扣减库存,财务进行收款,仓库进行发货等操作,设计时,可以把订单表与订单消息表共同存储到一个数据库里,当订单表写入的时候也同步写订单消息表,2个表同时写成功后,事务才算成功。然后再通过一个任务来查询订单消息表把订单消息表里的消息发送到MQ,当收到MQ的成功回执消息后,再把消息表里的消息进行删除。库存扣减、财务收款、仓库发货等均消费MQ里的该消息来完成各自的业务操作。该方式的操作流程模型如下:
在该方案模型中,当MQ发送方把消息发送后,长时间未收到MQ服务端的结果回执,此时需要再次发送该消息,这导致消息的发送可能会重复多次发送,需要消费方保证处理消息的接口幂等;该方案的优点是对业务的侵入小,核心业务只需要关注把自己业务做完的同时把需要发送的消息写入消息表即可。该方案也是在我们生产中用的比较多的方案之一。
在用消息队列处理的业务场景中,都存在发送方重复多次发送的可能,所以消费方都需要保证业务接口的幂等性,便于重复发送,重复消费时业务的幂等。消息队列在电商、互联网等业务中用的比较广泛,其作用主要体现在业务消峰,解耦业务,异步处理等场合。
至此,把目前学习到的,工作中使用到的分布式事务处理的常用方案及思路总结完毕。如果上述的内容有不正确或不妥之处,欢迎大家留言交流,谢谢!
作者:京东物流 廖宗雄
来源:京东云开发者社区 自猿其说Tech 转载请注明出处