【.com原稿】随着业务的快速发展和业务复杂度的增加,几乎每个公司的系统都会从单体走向分布式,尤其是微服务架构。图片来自宝途网再难免会遇到分布式事务的问题。而我的这篇文章总结了分布式事务的解决方案,希望对你有所帮助。分布式事务基础①事务到底是什么?什么是交易?举个生活中的例子:你去一个小商店买东西,“一只手付款,一只手送货”就是一个交易的例子,付款和送货都成功就一定是交易成功。如果任何活动失败,事务将撤消所有成功的活动。理解了上面的例子,再来看一个事务的定义:一个事务可以看作是一个大的活动,它是由不同的小活动组成的,这些活动要么都成功,要么都失败。②先回顾一下本地交易。在计算机系统中,更多的事务是通过关系数据库来控制的,它是利用数据库本身的事务特性来实现的,所以称为数据库事务。由于应用程序主要依靠关系数据库来控制事务,而数据库通常与应用程序在同一台服务器上,因此基于关系数据库的事务也称为本地事务。回顾一下数据库事务ACID的四大特性:A(Atomic):原子性,构成事务的所有操作要么执行要么根本不执行,不可能部分成功部分失败。C(Consistency):一致性,事务执行前后,数据库的一致性约束没有被打破。举个例子:张三给李四转了100块钱,转前后的数据都是正确的,这叫一致性。如果张三转了100元,而李四的账户并没有增加100元,就会出现数据错误。没有达到一致性。I(隔离):隔离。数据库中的事务通常是并发的。隔离是指两个并发事务的执行不会相互干扰。一个事务看不到其他事务的中间状态。通过配置事务隔离级别,可以避免脏读、重复读等问题。D(耐久性):持久性。事务完成后,事务对数据所做的修改会持久化到数据库中,不会回滚。当一个数据库事务实现时,一个事务中涉及的所有操作都会被纳入一个不可分割的执行单元。该执行单元中的所有操作要么成功要么失败。只要任何一个操作失败,都会导致整个Transaction回滚。③分布式事务银行跨行转账业务是典型的分布式事务场景。假设A需要跨行给B转账,那么涉及到两家银行的数据,通过一个数据库的本地事务无法保证转账的ACID,只能通过分布式业务来解决。分布式事务是指事务发起者、资源和资源管理者、事务协调者分别位于分布式系统的不同节点上。上述转账业务中,用户A-100操作和用户B+100操作不在同一个节点。本质上,分布式事务是为了保证分布式场景下数据操作的正确执行。分布式环境下的分布式事务,为了满足可用性、性能和降级服务的需求,降低一致性和隔离性的要求,一方面遵循BASE理论(BASE相关理论,涉及内容很多,有兴趣程序员,可以参考BASE理论)。BASE理论:基本可用性(BasicAvailability)软状态(Softstate)最终一致性(Eventualconsistency)同样,分布式事务也部分遵循ACID规范:原子性:严格遵循一致性:事务完成后的严格一致性事务中的一致性可以适当放宽隔离:并行事务不受影响;交易中间结果的可见性让安全放宽持久化:严格遵循④分布式交易场景典型场景是微服务架构:微服务之间通过远程调用完成交易操作。例如:订单微服务和库存微服务。下订单时,订单微服务请求库存微服务减少库存。简而言之:跨JVM进程的分布式事务。单体系统访问多个数据库实例:分布式事务发生在单体系统需要访问多个数据库(实例)时。例如:用户信息和订单信息分别存储在两个MySQL实例中。在用户管理系统中删除用户信息,需要分别删除用户信息和用户订单信息。由于数据分布在不同的数据实例中,需要通过不同的数据库链接来操作数据,这时就产生了分布式事务。简而言之:跨数据库实例的分布式事务。多个服务访问同一个数据库实例:比如订单微服务和库存微服务即使访问同一个数据库也会产生分布式事务。原因是两个微服务持有不同的数据库链接,用于跨JVM进程的数据库操作。这时候就会产生一个分布式事务。分布式事务解决方案①2PC(two-phasecommit)/XA2PC(Two-phasecommitprotocol),中文叫两阶段提交。两阶段提交是一种强一致性设计。2PC引入了一个事务协调者的角色来协调和管理每个参与者(也称为每个本地资源)的提交和回滚。第二阶段是指准备(投票)和提交两个阶段。XA是X/Open组织提出的分布式事务规范。XA规范主要定义了(全局)事务管理器(TM)和(本地)资源管理器(RM)之间的接口。MySQL等本地数据库在XA中扮演着RM的角色。XA分为两个阶段:第一阶段(prepare):即所有参与者RM准备执行事务并锁定所需资源。当参与者准备就绪时,它会向TM报告它已准备就绪。第二阶段(commit/rollback):当事务管理器(TM)确认所有参与者(RM)准备就绪后,向所有参与者发送commit命令。目前主流数据库基本都支持XA事务,包括MySQL、Oracle、SQLServer、Postgre等。XA事务由一个或多个资源管理器(RM)、一个事务管理器(TM)和一个应用程序(ApplicationProgram)组成。如果任何参与者准备失败,TM将通知所有已完成准备的参与者回滚。XA事务的特点是:简单易懂,易于开发,锁资源时间长,并发度低②三阶段提交(3PC)三阶段提交,又称3PC,增加了CanCommit阶段和与2PC机制相比的超时。如果一定时间内没有收到coordinator的commit请求,会自动执行commit,解决了2PC单点故障的问题。但性能问题和不一致问题仍然没有得到根本解决。接下来,我们来看看接下来的三个阶段流程是怎样的?第一阶段:CanCommit阶段。这个阶段做的事情很简单,就是协调者向交易参与者询问你是否有能力完成交易。如果都返回yes,则进入第二阶段。如果返回no或等待响应超时,则事务中止并向所有参与者发送中止请求。第二阶段:PreCommit阶段。此时协调器会向所有参与者发送PreCommit请求,参与者收到后开始进行事务操作,并在事务日志中记录Undo和Redo信息。参与者执行事务操作后(此时属于未提交事务状态),会向协调器反馈“Ack”,表示我准备提交,等待协调器的下一步操作。第三阶段:DoCommit阶段。在Phase2中,如果所有参与节点都可以进行PreCommit提交,协调器将从“precommit状态”变为“commit状态”。然后向所有参与节点发送“doCommit”请求。参与节点收到提交请求后,将执行事务提交操作,并向协调节点反馈“Ack”消息。协调者收到所有参与者的Ack消息后完成交易。反之,如果一个参与节点没有完成PreCommit反馈或者反馈超时,协调器就会向所有参与节点发送abort请求,从而中断交易。③SAGASaga是本篇数据库论文saga中提到的解决方案。它的核心思想是将一个长事务拆分成多个本地短事务,由Saga事务协调器协调。如果正常结束,则正常完成。如果某个步骤失败,将以相反的顺序调用一次补偿操作。以上述转账为例,成功完成SAGA事务的时序图如下:SAGA事务的特点:高并发,不会像XA事务那样长期锁定资源,需要定义正常操作和补偿操作,开发量比XA大,一致性比较差弱,对于转账,可能会出现用户A扣钱,最后转账失败④TCC2PC是数据库层面的,TCC是业务层面的分布式事务,就像我前面说的分布式事务不仅包括数据库操作,还包括发送短信等,这时候TCC就派上用场了!TCC的3个阶段:Try阶段:尝试执行,完成所有业务检查(一致性),预留必要的业务资源(准隔离)Confirm阶段:确认业务实际执行,不做任何业务检查,仅使用Try阶段预留的业务资源。Confirm操作需要幂等设计,Confirm失败后需要重试。取消阶段:取消执行,释放Try阶段预留的业务资源,也可以理解为取消预留阶段的动作。Cancel阶段的异常处理方案与Confirm阶段基本一致,需要幂等设计。以上述转账为例,金额通常在Try中冻结,但不扣除,在Confirm中扣除,在Cancel中解冻。成功完成TCC事务时序图如下:TCC特点如下:高并发,无长期资源锁定,开发量大,需要提供Try/Confirm/Cancel接口一致性,没有SAGA被扣除,最后在转账失败的情况下TCC适用于订单业务和对中间状态有约束的业务⑤本地消息表本地消息表实际上是利用各个系统的本地事务来实现分布式事务。顾名思义,本地消息表就是有一张表用来存放本地的消息,这个表一般是放在数据库中,然后在执行业务的时候,业务的执行和将消息放入消息表的操作都是放在同一个事务中,这样可以保证消息放到本地表中,业务一定执行成功。然后调用下一个操作。如果下次操作调用成功,则可以直接将消息表的消息状态改为成功。调用失败也没关系,会有后台任务定时读取本地消息表,过滤掉没有调用成功的消息,然后调用相应的服务,服务更新后改变消息的状态成功的。这时候可能消息对应的操作不成功,所以也需要重试。重试必须保证对应的服务方法是幂等的,一般会有最大重试次数。如果超过最大重试次数,可以记录告警,供人工处理。.可以看出,本地消息表实际上实现了最终一致性,容忍临时数据不一致。⑥消息事务RocketMQ对消息事务的支持非常好,我们来看看如何通过消息实现事务。第一步是向Broker发送交易消息,即半消息。halfmessage不是消息的一半,而是消息对consumer是不可见的,发送成功后再由sender执行本地事务。然后根据本地事务的结果向Broker发送Commit或者RollBack命令。而RocketMQ的发送方会提供一个接口来查看交易状态。如果一段时间内没有收到操作请求,Broker会通过接口知道发送方的交易是否成功,然后执行Commit或者RollBack命令。如果是Commit,那么订阅者就可以收到这条消息,然后做相应的操作,完成后再消费这条消息。如果是RollBack,订阅者是收不到这条消息的,说明事务还没有执行。可见通过RocketMQ实现起来还是比较容易的。RocketMQ提供了事务消息的功能。我们只需要定义交易反向查询接口即可。同时也可以看到消息事务也是最终一致性的。⑦BestEffortNotification发起通知者通过一定的机制尽最大努力将业务处理结果通知给接收者。具体包括:有一定的消息重复通知机制。因为通知的接收者可能还没有收到通知,这时候肯定有一定的机制来重复通知消息。消息整理机制。如果接收者尽最大努力仍未收到通知,或者接收者消费消息后想再次消费消息,则接收者可以主动向通知者查询消息信息以满足需求。上面描述的本地消息表和消息事务都是可靠的消息。它与此处介绍的尽力而为通知有何不同?为了可靠的消息一致性,发起通知者需要保证消息被发送出去,消息被发送给接收通知者。消息可靠性的关键在于通知方的保证。尽力通知。发起方尽量将业务处理结果通知通知方,但可能收不到消息。在这种情况下,通知方需要主动调用通知方接口查询业务处理结果和通知的可靠性。关键在于通知的接收者。在解决方案上,besteffortnotification需要:提供一个接口,让通知的接收者可以通过接口查询业务处理结果消息队列ACK机制,消息队列遵循1min、5min、10min、30min、1h、2h、5h、10h,逐渐增加通知间隔,直到达到通知要求的时间窗口上限。之后,best-effort通知适用于业务通知类型。例如微信交易的结果通过best-effort通知的方式通知给各个商户。既有回调通知接口,也有交易查询接口。⑧AT交易模式这是阿里开源项目seata中的一种交易模式,也就是蚂蚁金服中的FMT。好处是这种事务模式的用法和XA模式类似。业务无需编写各种补偿操作,回滚由框架自动完成。缺点也和AT类似。有长期锁,不满足高并发场景。在Seata项目中,阿里巴巴中间件率先开源的AT模式(AutomaticTransaction)是一套创新的、非侵入式的分布式事务解决方案。自SeataGA版本发布以来,AT模式在开源社区引起了广泛关注,许多企业用户已经将Seata的AT模式应用到生产中。AT模式第一阶段:首先,在Seata组件中,如果要开启分布式事务,需要在业务入口或事务发起入口添加@GlobalTransactional注解。如果是AT模式,必须做好数据源代理(seata1.0后全面支持自动代理),被sqlsessionfactroy使用(或者直接使用代理数据源进行jdbc操作)。可以发现关键是异步,区别于其他模式的是代理数据源,而代理数据源有什么玄机呢?AT模式两阶段提交:如果是第二阶段提交,因为“业务SQL”已经在第一阶段提交到数据库,所以Seata框架只需要删除第一阶段保存的快照数据和行锁阶段完成数据清洗。AT模式下的两阶段回滚:如果是第二阶段的回滚,Seata需要回滚第一阶段已经执行过的“业务SQL”来恢复业务数据。回滚方式是使用“之前的映像”来恢复业务数据;但在恢复前,先检查脏写,对比“数据库当前业务数据”和“残影”。如果两者数据完全一致,说明没有脏写,可以恢复业务数据。如果不一致,说明有脏写。如果有脏写,需要人工处理。总结本文介绍了分布式事务的一些基本理论,并对常用的分布式事务解决方案进行了说明。分布式事务本身就是一个技术问题,在业务中采用哪种方案还是需要根据不同的业务特点来选择。但我们也会发现,分布式事务会大大增加流程的复杂度,带来大量额外的开销工作,“代码量增加,业务变得复杂,性能下降”。所以,我们在实际开发的时候,能不使用分布式事务也可以不使用。作者:JackHu简介:水滴健康基础设施高级技术专家编辑:陶佳龙征稿:有意投稿或寻求报告,请联系editor@51cto.com【原创稿件请注明原作者及来源为.com]
