分布式事务的理解

事务

定义:由一组操作构成的 可靠 独立 的工作单元,要么全部成功,要么全部失败

数据库事务:是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成;

通俗的理解,事务是一组原子操作单元,是并发控制的基本单元。从数据库角度说,就是一组SQL指令,要么全部执行成功,若因为某个原因其中一条指令执行有错误,则撤销先前执行过的所有指令。更简答的说就是:要么全部执行成功,要么撤销不执行

例如:银行转账工作,从一个帐号扣款并使另一个帐号增款,这个两个操作,要么都执行,要么都不执行。

事务必须服从ISO/IEC所制定的ACID原则。ACID是原子性(Atomicity)一致性(Consistency)隔离性 (Isolation)和持久性(Durability)的缩写。

  • 原子性(Atomicity) :原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
  • 一致性(Consistency):一个事务在执行之前和执行之后 数据库都必须处于一致性状态。如果事务成功的完成,那么数据库的所有变化将生效;如果事务执行出现错误,那么数据库的所有变化将会被回滚(撤销),返回到原始状态。
  • 隔离性(Isolation):多个用户并发的访问数据库时,一个用户的事务不能被其他用户的事务干扰,多个并发的事务之间要相互隔离。
  • 持久性:指一个事务一旦被提交,它对数据库的改变将是永久性的,接下来即使数据库发生故障也不会对数据产生影响。

分布式事务引入

本地事务

事务由资源管理器(如数据库管理系统 Database Management System-DBMS,所有的操作基于同一个数据库实例,由数据库保障事务)本地管理,如下图:

优点

  • 支持严格的ACID属性
  • 可靠 、高效
  • 状态可以只在资源管理器中维护
  • 应用编程模型简单(在框架或平台的支持) 局限
  • 不具备分布事务处理能力
  • 隔离的最小单位由资源管理器决定,如数据库中的一条记录

缺点

  • 不具备分布事务处理能力
  • 隔离的最小单位由资源管理器决定,如数据库中的 一条记录

本地事务解决方案:

  1. 配置事务管理器。spring提供了一个PlatformTransactionManager接口,其有2个重要的实现类:
    1. DataSourceTransactionManager:用于支持本地事务,事实上,其内部也是通过操作java.sql.Connection来开启、提交和回滚事务。    
    2. JtaTransactionManager:用于支持分布式事务,其实现了JTA规范,使用XA协议进行两阶段提交。需要注意的是,这只是一个代理,我们需要为其提供一个JTA provider,一般是Java EE容器提供的事务协调器(Java EE server's transaction coordinator),也可以不依赖容器,配置一个本地的JTA provider。  
  2. 在需要开启的事务的bean的方法上添加@Transitional注解  可以看到,spring除了支持本地事务,也支持分布式事务

分布式事务

当下大多数互联网公司都进行了数据库拆分和服务化(SOA)治理。在这种情况下,完成某一个业务功能可能需要横跨多个服务,操作多个数据库。这就涉及到到了分布式事务,用需要操作的资源位于多个资源服务器上,而应用需要保证对于多个资源服务器的数据的操作,要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同资源服务器的数据一致性。

分布式事务-跨库事务

操作不是基于同一个数据库实例,无法通过数据库保障事务,需要引入分布式事务解决方案

     

分布式事务-微服务

调用的多个服务的数据库服务不是基于同一个数据库实例

分布式事务的概念

分布式事物: 指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上,简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。而广义上来说一次请求涉及数据分布多个存储系统的事务操作都叫分布式事务,比如多个数据库,数据库和缓存或者数据库和MQ。

当我们的单个数据库的性能产生瓶颈的时候,我们可能会对数据库进行分库或者分区,这个时候单个数据库的ACID已经不能适应这种情况了,引入了分布式事务

分布式事务的理论基础

CAP理论

                                                                   

CAP定理是由加州大学伯克利分校Eric Brewer教授提出,指一个分布式系统不可能同时满足一致性(Consistency )、可用性 ( Availability )和分区容错性(Partition Tolerance)这三个基本需求,最多只能同时满足其中2项。

  • C (一致性):在分布式环境中,一致性是指数据在多个副本(节点)之间能够保持一致,即“all nodes see the same data at the same time”,对某个指定的客户端来说,读操作能返回最新的写操作。当一个系统在数据一致的状态下执行更新操作后,应该保证系统的数据仍然处于一致的状态(如对第一个节点对数据进行更新操作并且更新成功,却没有对第二个节点的数据更新导致第二个节点在读取数据时候读取的是脏数据,这就是典型的分布式数据不一致情况),在分布式系统中如果能够做到针对一个数据的更新执行成功后所有用户都可以读取最新的数据,那么认为这样的系统具有强一致性
  • A (可用性):可用性指系统提供的服务必须一直处于可用的状态,对于用户的每个操作都会在有限的时间内返回结果,在集群中一部分节点故障后,集群整体还能响应客户端的读写请求。
  • P (分区容错性):指分布式系统在遇到任何网络分区故障时候,仍然需要能够对外提供满足一致性和可用性的服务,除非整个网络环境都发生故障(网络分区指分布式系统中不同节点由于分布在不同的子网络,由于特殊原因而导致子网络之间出现网络不通的状态,但是各个子网络内部网络是正常的,从而导致整个系统的网络环境被切分若干个孤立的区域),比如集群有多台机器,有台机器网络出现了问题,但是这个集群仍然可以正常工作,以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在CA之间做出选择。

注意:

 关于一致性,如果的确能像上面描述的那样时刻保证客户端看到的数据都是一致的,那么称之为强一致性。如果允许存在中间状态,只要求经过一段时间后,数据最终是一致的,则称之为最终一致性。此外,如果允许存在部分数据不一致,那么就称之为弱一致性

CAP三个特性只能满足其中两个,那么取舍的策略就共有三种:

  • CA without P:如果不要求P(不允许分区),则C(强一致性)和A(可用性)是可以保证的。但放弃P的同时也就意味着放弃了系统的扩展性,也就是分布式节点受限,没办法部署子节点,这是违背分布式系统设计的初衷的。传统的关系型数据库RDBMS:Oracle、MySQL就是CA。
  • CP without A:如果不要求A(可用),相当于每个请求都需要在服务器之间保持强一致,而P(分区)会导致同步时间无限延长(也就是等待数据同步完才能正常访问服务),一旦发生网络故障或者消息丢失等情况,就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统。设计成CP的系统其实不少,最典型的就是分布式数据库,如Redis、HBase等。对于这些分布式数据库来说,数据的一致性是最基本的要求,因为如果连这个标准都达不到,那么直接采用关系型数据库就好,没必要再浪费资源来部署分布式数据库。
  •  AP wihtout C:要高可用并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。典型的应用就如某米的抢购手机场景,可能前几秒你浏览商品的时候页面提示是有库存的,当你选择完商品准备下单的时候,系统提示你下单失败,商品已售完。这其实就是先在 A(可用性)方面保证系统可以正常的服务,然后在数据的一致性方面做了些牺牲,虽然多少会影响一些用户体验,但也不至于造成用户购物流程的严重阻塞。

既然一个分布式系统无法同时满足一致性、可用性、分区容错性三个特点,我们就需要抛弃一个,需要明确的一点是,对于一个分布式系统而言,分区容错性是一个最基本的要求。因为既然是一个分布式系统,那么分布式系统中的组件必然需要被部署到不同的节点,否则也就无所谓分布式系统了。而对于分布式系统而言,网络问题又是一个必定会出现的异常情况,因此分区容错性也就成为了一个分布式系统必然需要面对和解决的问题。因此系统架构师往往需要把精力花在如何根据业务特点在C(一致性)和A(可用性)之间寻求平衡。而后面我们提到的X/Open XA 两阶段提交协议的分布式事务方案,强调的就是一致性;由于可用性较低,实际应用的并不多。而基于BASE理论的柔性事务,强调的是可用性,大部分互联网公司采可能会优先采用这种方案。

  • 对于CP来说,放弃可用性,追求一致性和分区容错性,如zookeeper其实就是追求的强一致。
  • 对于AP来说,放弃一致性(这里说的一致性是强一致性),追求分区容错性和可用性,spring cloud追求的就是可用性,后面的BASE也是根据AP来扩展。

BASE理论

BASE Basically Available(基本可用)Soft state(软状态) Eventually consistent (最终一致性)三个短语的缩写。是由eBay的架构师Dan Pritchett提出,BASE理论是对CAP中一致性和可用性权衡的结果,来源于大规模互联网系统分布式实践的总结,基于CAP演化而来,核心思想是:即使无法做到强一致性(Strong consistency),但每次应用都可以根据自身业务特点,采用适当的方式使系统达到最终一致性(Event consistency)。BASE理论是对CAPAP的一个扩展,用软状态和最终一致性。BASE ACID 是相反的,它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。

  • 基本可用:分布式系统在出现故障时,允许损失部分可用功能(这不等价系统不可用,而是比如:响应时间的损失,原来0.5s返回响应,现在需要1-2s,或功能的损失,在大并发请求下部分用户被引导到降级页面等),保证核心功能可用。
  • 软状态:允许系统中存在中间状态,这个状态不影响系统可用性,即允许系统在不同节点的数据副本之间进行同步的过程存在延时
  • 最终一致:最终一致是指经过一段时间后,所有节点数据都将会达到一致的状态,因此最终一致性的本质是需要系统保证数据最终一致而不是实时一致的强一致性

BASE理论面向的是大型高可用可扩展的分布式系统,和传统的事物ACID特性是相反的。它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。但同时,在实际的分布式场景中,不同业务单元和组件对数据一致性的要求是不同的,因此在具体的分布式系统架构设计过程中,ACID特性和BASE理论往往又会结合在一起。

2PC和3PC协议 

在分布式系统中,每一个机器节点虽然都能够明确地知道自己在进行事务操作过程中的结果是成功或失败,但却无法直接获取到其他分布式节点的操作结果。因此,当一个事务操作需要跨越多个分布式节点的时候,为了保持事务处理的 ACIO 特性,就需要引入一个称为“协调者”( Coordinator) 的组件来统一调度所有分布式节点的执行逻辑,这些披调度的分布式节点则被称为“参与者” ( Participant )。协调者负责调度参与者的行为,并最终决定这些参与者是否要把事务真正进行提交。基于这个思想,衍生出了二阶段提交和三阶段提交两种协议.

二阶段提交(2PC)

两阶段提交协议(Two Phase Commit),通常二阶段提交协议被认为是一种一致性协议,用来保证分布式系统数据的一致性,目前绝大多数关系型数据库都采用的是二阶段提交协议来完成分布式事务处理的,该协议可以方便的完成分布式事务参与者的协调统一决定事务的提交或回滚,有效的保证分布式数据一致性,广泛的应用于分布式系统,主要分为二阶段来处理,流程如下

主要分为:准备阶段 (操作)和提交阶段

第一阶段:提交事务请求阶段,根据功能也可称为投票阶段(Commit-request phase)

  • 事务询问:协调者向所有的参与者发送事务内容,询问是否可以执行事务提交操作,并等待参与者响应;
  • 执行事务:将各参与者执行提交事务操作,记录Undo和Redo信息到事务日志;

反馈事务询问的响应:如果参与者成功执行事务操作,反馈YES响应,表示事务可以执行,否则返回NO表示事务不可以执行

第二阶段:执行事务的提交阶段(Commit phase): 

情况一:如果事务管理器收到的所有回应都是YES响应,则执行事务提交

  • 发送提交请求:协调者向所有参与者节点发送Commit请求
  • 事务提交:收到Commit请求后,执行事务提交操作,提交完成后释放事务资源

反馈事务提交结果:事务提交后,向协调者发送ACK消息,协调者接收到所有参与者反馈的ACK消息后,完成事务

情况二:如果任何一个参与者向协调者反馈了NO响应,或等待超时后还未收到所有参与者的反馈响应,则中断事务

  • 发送回滚请求:协调者向所有参与者节点发出Rollback请求
  • 事务回滚:参与者收到Rollback请求后,利用一阶段记录的Undo信息执行事务回滚操作,完成回滚后释放资源

反馈事务回滚结果:回滚完成后,向协调者发送ACK消息,收到所有参与者返回的ACK消息后,完成事务中断

具体过程如下图:

二阶段“事务提交”示意图如下:

二阶段“事务回滚”示意图

2PC的优缺点

优点:原理简单,实现方便, 尽量保证了数据的强一致,实现成本较低,两阶段提交协议(Two Phase Commit)不是在XA规范中提出,但是XA规范对其进行了优化,在各大主流数据库都有自己实现,又叫做 XA Transactions。

MySQL从5.5版本开始支持,SQL Server 2005 开始支持,Oracle 7 开始支持。

缺点:同步阻塞,单点问题,脑裂,过于保守

  • 同步阻塞:二阶段提交过程中,所有参与该事务操作的逻辑都处理阻塞状态,各个参与者在等待其他参与者的响应过程中无法进行其他的任何操作,极大的限制分布式系统的性能
  • 单点问题:协调者在二阶段提交过程中起到非常重要的左右,一旦协调者出现问题,二阶段流程无法运转,并且其他参与者将会一直处于锁定事务资源的状态无法继续完成事务操作
  • 数据不一致:在二阶段提交的阶段二,协调者向所有的参与者发送Commit请求后,发生局部网络异常或者尚未发送完Commit请求自身发生崩溃,导致只有部分参与者收到Commit请求,从而导致部分事务提交部分事务无法提交,继而引起数据不一致的现象
  • 太过保守:对于参与者出现故障而无法响应,协调者只有利用超时来决定是否中断事务,这种策略显得比较保守,换句话说二阶段提交没有完善的容错机制,任何一个节点的失败都导致整个事务的失败。

缺点:

  • 单点问题:协调者(事务管理器TM)在整个流程中扮演的角色很关键,如果其宕机,参与者RM会一直阻塞下去。如第一阶段已经完成,在第二阶段正准备提交的时候事务管理器TM宕机,参与者RM还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者TM挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)。
  • 同步阻塞:数据库在提交请求阶段应答后对很多资源处于锁定状态,要等到事务管理器收集齐所有数据库的应答后,才能发commit或者rollback消息结束这种锁定。所有参与该事务操作的逻辑都处理阻塞状态,各个参与者在等待其他参与者的响应过程中无法进行其他的任何操作,极大的限制分布式系统的性能
  • 数据不一致:两阶段提交协议虽然为分布式数据强一致性所设计,但仍然存在数据不一致性的可能,如果在二阶段提交的阶段二,协调者向所有的参与者发送Commit请求后,发生局部网络异常或者尚未发送完Commit请求自身发生崩溃,导致只有部分参与者收到Commit请求,从而导致部分事务提交部分事务无法提交,继而引起数据不一致的现象
  • 性能不理想,XA无法满足并发量场景下,XA目前在商业数据库支持的比较理想,但在mysql数据库中支持的不太理想,mysqlXA实现,没有记录prepare阶段日志,主备切换回导致主库与备库数据不一致。许多nosql也没有支持XA,这让XA的应用场景变得非常狭隘
  • 事务管理发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交(如果某个资源管理器Commit失败可以进行重试的操作,记录日志多次失败重试人工处理)

如下是mysql5.7官方文档关于对XA分布式事务的支持有以下介绍:MySQL :: MySQL 5.7 Reference Manual :: 13.3.7 XA Transactions

 A global transaction involves several actions that are transactional in themselves, but that all must either complete successfully as a group, or all be rolled back as a group. In essence, this extends ACID properties “up a level” so that multiple ACID transactions can be executed in concert as components of a global operation that also has ACID properties. (As with nondistributed transactions, SERIALIZABLE may be preferred if your applications are sensitive to read phenomena. REPEATABLE READ may not be sufficient for distributed transactions.)

由于二阶段提交存在着诸如同步阻塞、单点问题等缺陷,所以,研究者们在二阶段提交的基础上做了改进,提出了三阶段提交。

JAVA的开源的实现:JTA(Java Transaction Manager)规范的框架:atomikos框架(性能较差,基本不用)

三阶段提交(3PC)

3PC是Three-Phase Commit的缩写,即三阶段提交,它是二阶段提交的改进版,主要改动点如下:

  • 同时在协调者和参与者中都引入超时机制。
  • 将二阶段提交协议的“提交事务请求”过程一分为二,形成了由CanCommit、PreCommit和DoCommit三个阶段组成的事务处理协议

阶段一:CanCommit阶段

3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应

  • 事务询问 协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。
  • 响应反馈 参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态。否则反馈No

阶段二:PreCommit阶段

协调者根据参与者的反馈情况来决定是否可以进行事务的PreCommit操作。根据响应情况,有以下两种可能。
假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务预提交

  • 发送预提交请求:协调者向所有参与者节点发送preCommit的请求,并进入Prepared阶段
  • 事务预提交:参与者收到preCommit请求后,会执行事务操作,并将Undo和Redo信息记录到事务日志中
  • 反馈事务执行:参与者成功执行事务操作,反馈ACK响应给协调者,等待最终的提交或者中止指令

假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务中断

  • 发送中断请求:协调者向所有参与者节点发出abort请求
  • 中断事务:无论收到协调者的abort请求还是等待协调者请求过程中出现超时,参与者都会中断事务

阶段三:doCommit阶段

该阶段进行真正的事务提交,也可以分为以下两种情况
执行提交

  • 发送提交请求: 协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。
  • 事务提交: 参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。
  • 响应反馈: 事务提交完之后,向协调者发送Ack响应。
  • 完成事务:协调者接收到所有参与者的ack响应之后,完成事务。

中断事务 协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务

  • 发送中断请求 协调者向所有参与者发送abort请求
  • 事务回滚 参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。
  • 反馈结果 参与者完成事务回滚之后,向协调者发送ACK消息
  • 中断事务 协调者接收到参与者反馈的ACK消息之后,执行事务的中断。

在doCommit阶段,如果参与者无法及时接收到来自协调者的doCommit或者rebort请求时,会在等待超时之后,会继续进行事务的提交。(其实这个应该是基于概率来决定的,当进入第三阶段时,说明参与者在第二阶段已经收到了PreCommit请求,那么协调者产生PreCommit请求的前提条件是他在第二阶段开始之前,收到所有参与者的CanCommit响应都是Yes。(一旦参与者收到了PreCommit,意味他知道大家其实都同意修改了)所以,一句话概括就是,当进入第三阶段时,由于网络超时等原因,虽然参与者没有收到commit或者abort响应,但是他有理由相信:成功提交的几率很大)

进入阶段三,如果出现以下两种故障:

  • 协调者出现问题
  • 协调者和参与者网络出现问题

都会导致参与者无法及时收到来自协调者的doCommit或者abort请求,参与者都会在等待超时后,继续进行事务提交.

3PC的优缺点

  • 优点:相较于二阶段的提交协议,三阶段的提交协议最大的优点就是降低了参与者的阻塞范围,并且能够在出现单点故障后继续达成一致
  • 缺点:去除阻塞的同时引入了新的问题,那就是在参与者接收到preCommit消息后,如果网络出现分区,协调者参与者无法通信,此时参与者依然会进行事务提交,从而导致数据不一致

2PC与3PC的区别

相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况

分布式事务的解决方案

分布式事务的解决方案主要分为2种类型,一种是基于全局的事务管理器,另外一种是基于业务状态实现事务,主要的如下:

模式

常用方案

基本原理

基于全局事务管理器

XA协议

基于数据库实现的方案

TCC

Try,Confirm,Cancel,完全由业务侧实现

Saga模式

正向操作成功后,不做任何处理,失败后,执行提前生成的回滚SQL

AT模式

类似于saga,但基于UNDO LOG实现回滚

基于业务状态实现

基于BASE原则的最终一致方案

源于ebay,基本可用、柔性状态、最终一致

X/Open DTP模型与XA规范(基于XA协议的2阶段提交

    X/Open,即现在的open group,是一个独立的组织,主要负责制定各种行业技术标准。官网地址:The Open Group Website |。X/Open组织主要由各大知名公司或者厂商进行支持,这些组织不光遵循X/Open组织定义的行业技术标准,也参与到标准的制定。下图展示了open group目前主要成员(官网截图): 

9A3AC451-E44F-47FB-A7D6-20FD18B65723.png

就分布式事务处理(Distributed Transaction Processing,简称DTP)而言,X/Open主要提供了以下参考文档:

   DTP 参考模型: <<Distributed Transaction Processing: Reference Model>>

   DTP XA规范: << Distributed Transaction Processing: The XA Specification>>

(DTP模型)

X/Open XA 协议

最早的分布式事务模型是 X/Open 国际联盟提出的 X/Open Distributed Transaction Processing(DTP)模型,也就是大家常说的 X/Open XA 协议,简称XA 协议。涉及多个分布在不同地方的数据库,但对数据库的操作必须全部被提交或者回滚。只要任一数据库操作时失败,所有参与事务的数据库都需要回滚。

Open  组织定义的分布式事务处理模型X/Open DTP  模型(1994)包括应用程序(  AP  )、事务管理器(  TM  )、资源管理器(  RM ,即数据库 )、通信资源管理器(  CRM  )四部分

XA 就是 X/Open DTP 定义的交易中间件与数据库之间的接口规范(即接口函数),交易中间件用它来通知数据库事务的开始、结束以及提交、回滚等。 XA 接口函数由数据库厂商提供,如Mysql、Oracle等。

二阶提交协议和三阶提交协议就是根据这一思想衍生出来的。可以说二阶段提交其实就是实现XA分布式事务的关键(确切地说:两阶段提交主要保证了分布式事务的原子性:即所有结点要么全做要么全不做)

  • AP(Application Program):应用程序,主要是定义事务边界以及那些组成事务的特定于应用程序的操作。
  • RM(Resouces Manager):资源管理器,管理一些共享资源的自治域,如提供对诸如数据库之类的共享资源的访问,资源必须实现XA定义的接口。
  • TM(Transaction Manager):事务管理器,管理全局事务,分配事务唯一标识,协调事务的提交或者回滚,并协调故障恢复。

一般,常见的事务管理器( TM )是交易中间件,常见的资源管理器( RM )是数据库,常见的通信资源管理器( CRM )是消息中间件。 分布式事务处理的对象是全局事务。 所谓全局事务,是指分布式事务处理环境中,多个数据库可能需要共同完成一个工作,这个工作即是一个全局事务;事务由全局事务管理器全局管理,事务管理器管理全局事务状态与参与的资源,协同资源的一致提交/回滚。

  • TX协议:应用或应用服务器与事务管理器的接口
  • XA协议: 全局事务管理器与资源管理器的接口,在DTP分布式事务模型中,XA规范除了定义的RM-TM交互的接口,即TM与数据库之间的接口规范,TM用它来通知数据库事务的开始、结束以及提交回滚等。而XA接口函数由数据库厂商提供

三者之间的关系:

http://image20.it168.com/201806_335x251/3211/28e21ec107a856d4.png

DTP 模型中包含一个全局事务管理器(TM,Transaction Manager)和多个资源管理器(RM,Resource Manager)。全局事务管理器负责管理全局事务状态与参与的资源,协同资源一起提交或回滚;资源管理器则负责具体的资源操作。XA 协议描述了 TM 与 RM 之间的接口,允许多个资源在同一分布式事务中访问。

基于 DTP 模型的分布式事务流程大致如下:

主要流程如下:

  1. 应用程序(AP,Application)向 TM 申请开始一个全局事务。
  2. 针对要操作的 RM,AP 会先向 TM 注册(TM 负责记录 AP 操作过哪些 RM,即分支事务),TM 通过 XA 接口函数通知相应 RM 开启分布式事务的子事务,接着 AP 就可以对该 RM 管理的资源进行操作。
  3. 当 AP 对所有 RM 操作完毕后,AP 根据执行情况通知 TM 提交或回滚该全局事务,TM 通过 XA 接口函数通知各 RM 完成操作。TM 会先要求各个 RM 做预提交,所有 RM 返回成功后,再要求各 RM 做正式提交,XA 协议要求,一旦 RM 预提交成功,则后续的正式提交也必须能成功;如果任意一个 RM 预提交失败,则 TM 通知各 RM 回滚。
  4. 所有 RM 提交或回滚完成后,全局事务结束。

局限

  • 更高协议成本 •
  • 脆弱,故障点多 故障影响大,恢复困难
  • 复杂,更多架构与平台约束
  • 性能
框架
开源框架atomikos 分两个版本:
  • TransactionEssentials:开源的免费产品
  • ExtremeTransactions:上商业版,需要收费

本地消息表

可靠消息实现分布式事务,

在有A、B系统的分布式微服务的情况下,实现A、B系统的基于可靠消息的分布式事务主要步骤如下,以下单和扣库存这个经典的例子

  1. A系统除了业务订单表外,还需要创建一个消息表,A系统的订单表的操作和消息表的操作处于同一个本地事务下,每新增一笔订单表数据同时,新增一个消息表数据(同一事物)
  2. A系统的异步线程,读取消息表,发送MQ消息(可以是任意的消息中间件),消息发送成功,ACK给A系统,收到ACK后可以删除或者标记已处理(确保消息不会丢失)。
  3. 如果没有收到ACK或者发送消息失败,可以将发送失败的消息记录到队列中,异步补偿线程再次处理、发送。
  4. B系统监听消费消息,收到消息后,进行业务处理,扣除库存表的数据,因为消息可能重复发送,B系统消费消息处理业务逻辑必须保持幂等性,B系统的业务处理不会影响A系统的处理,如果B系统处理消费消息处理业务失败,通过记录日志,线下手动处理等,保持A和B的最终一致性。

优点:依赖于可靠消息,实现简单,成本低

缺点:保证最终一致性,对于一致性要求较高的情况下不适用

实现的核心点和实现方式:

  • 保证上游系统的业务处理和消息落库处于同一事务,同时保证消息在事务提交后(上游系统业务处理成功)才发送。主要通过Spring中@Transactional注解使得业务处理和消息库处于同一事务,通过spring事务管理器,拿到事务提交成功的事件后,触发消息发送,发送成功,删除消息存储。
  • 下游消费系统消费消息后的业务处理逻辑必须保证幂等,同时需要记录日志和做好监控,对于多次消费不成功的,提供记录报警处理。
  • 整个分布式事务需要做好监控记录日志,监控长时间未发送消息的msg表数据、监控消息系统死性队列、监控消费消息失败的情况

流程图如下:

注意要点:

  • 基于可靠消息,能够保证上下游系统数据的最终一致性
  • 可以通过extend TransactionSynchronizzationAdapter类监听事务处理状态,事务提交完成后启用线程发送消息。
  • 如果上游业务几次业务操作有先后关系,但由于消息系统乱序,下游系统一定要做好自己的状态机控制,严格执行状态机
  • 对一些一致要求高的业务,需要增加通过业务ID来进行数据确认的逻辑
  • 对于消息发送失败,或者多次消息处理不成功造成消息滞留未删除,需要记录并回滚业务逻辑,同样下游系统多次消费消息失败(保证幂等性),记录日志,告警,依赖人工处理,对于上下游回滚操作都需要根据对方的状态去判断是否回滚,是否重试等,确保不会发生不一致

可靠消息的其他实现方式:

  1. 上游系统在业务事务提交前,向实时消息服务请求发送消息,实时消息服务只记录消息数据,而不真正发送
  2. 上游系统在业务事务提交后,向实时消息服务确认发送。只有在得到确认发送指令后,实时消息服务才真正发送消息
  3. 上游系统在业务事务回滚后,向实时消息服务取消发送
  4. 消息状态确认系统定期找到未确认发送 或回滚发送的消息,向业务处理服务询 问消息状态,业务处理服务根据消息ID 或消息内容确定该消息是否有效
ECEB0CD9-A61C-4ED9-98C1-CE02A9B658C3.png

TCC 模式

TCC与XA/JTA对比 XA是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。 TCC是业务层面的分布式事务,最终一致性,不会一直持有资源的锁。 TCC的开源框架实现     Atomikos,tcc-transaction,spring-cloud-rest-tcc,支付宝tcc

第一阶段:消息落地数据库(order订单表和msg消息表在同一数据库,处于同一本地事务)

第二阶段:读取msg表中消息不断重试确保发送MQ(下游消费消息保证幂等)

可靠消息最终一致性方案

这个的意思,就是干脆不要用本地的消息表了,直接基于MQ来实现事务。比如阿里的RocketMQ就支持消息事务。

大概的意思就是:

1)A系统先发送一个prepared消息到mq,如果这个prepared消息发送失败那么就直接取消操作别执行了

2)如果这个消息发送成功过了,那么接着执行本地事务,如果成功就告诉mq发送确认消息,如果失败就告诉mq回滚消息

3)如果发送了确认消息,那么此时B系统会接收到确认消息,然后执行本地的事务

4)mq会自动定时轮询所有prepared消息回调你的接口,问你,这个消息是不是本地事务处理失败了,所有没发送确认消息?那是继续重试还是回滚?一般来说这里你就可以查下数据库看之前本地事务是否执行,如果回滚了,那么这里也回滚吧。这个就是避免可能本地事务执行成功了,别确认消息发送失败了。

5)这个方案里,要是系统B的事务失败了咋办?重试咯,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如B系统本地回滚后,想办法通知系统A也回滚;或者是发送报警由人工来手工回滚和补偿

这个还是比较合适的,目前国内互联网公司大都是这么玩儿的,要不你举用RocketMQ支持的,要不你就自己基于类似ActiveMQ?RabbitMQ?自己封装一套类似的逻辑出来,总之思路就是这样子的

最大努力通知方案

这个方案的大致意思就是:

1)系统A本地事务执行完之后,发送个消息到MQ

2)这里会有个专门消费MQ的最大努力通知服务,这个服务会消费MQ然后写入数据库中记录下来,或者是放入个内存队列也可以,接着调用系统B的接口

3)要是系统B执行成功就ok了;要是系统B执行失败了,那么最大努力通知服务就定时尝试重新调用系统B,反复N次,最后还是不行就放弃

TCC 补偿型 

基于消息的最终一致性

此方案的核心是将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。人工重试更多的是应用于支付场景,通过对账系统对事后问题的处理。 

  1. 基于本地消息表

在服务A中,操作A表,同时同库新增一个消息表,在同一个本地事物中操作A表(业务操作)和消息表(插入一条消息数据,状态是“待发送”)

异步读取消息库执行任务:发送状态是“待发送”的消息,发送次数+1;发送成功修改消息的状态为“已发送”,发送失败则下次继续发送,发送次数++;如果超过设置值都没有发送成功,则回滚操作A,并删除消息

在服务B中,监听消息队列,获取到消息后执行操作B表(确认没执行的情况下操作B表,保证幂等性),执行成功后,确认ACK给消息队列

这个时候就会出现消费失败和消费超时两个问题,解决超时问题的思路就是一直重试,直到消费端消费消息成功,整个过程中有可能会出现消息重复的问题,所以保证幂等性即可。但是如果消费失败怎么办?阿里提供给我们的解决方法是:人工解决。

  1. 基于MQ消息

TCC补偿事物

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值