事务的具体定义
事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。在关系数据库中,一个事务由一组SQL语句组成。事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
事务提供一种机制将一个活动涉及的所有操作纳入到一个不可分割的执行单元,组成事务的所有操作只有在所有操作均能正常执行的情况下方能提交,只要其中任一操作执行失败,都将导致整个事务的回滚。
简单地说,事务提供一种“要么什么都不做,要么做全套(All or Nothing)”机制。
数据库本地事务
ACID
说到数据库事务就不得不说,数据库事务中的四大特性 ACID:
A:原子性(Atomicity),一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。
事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
就像你买东西要么交钱收货一起都执行,要么发不出货,就退钱。
C:一致性(Consistency),事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。
如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。
如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。
I:隔离性(Isolation),指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。
由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
打个比方,你买东西这个事情,是不影响其他人的。
D:持久性(Durability),指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。
即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
打个比方,你买东西的时候需要记录在账本上,即使老板忘记了那也有据可查。
InnoDB 实现原理
InnoDB 是 MySQL 的一个存储引擎,大部分人对 MySQL 都比较熟悉,这里简单介绍一下数据库事务实现的一些基本原理。
在本地事务中,服务和资源在事务的包裹下可以看做是一体的,如下图:
我们的本地事务由资源管理器进行管理:
而事务的 ACID 是通过 InnoDB 日志和锁来保证。事务的隔离性是通过数据库锁的机制实现的,持久性通过 Redo Log(重做日志)来实现,原子性和一致性通过 Undo Log 来实现。
Undo Log 的原理很简单,为了满足事务的原子性,在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为 Undo Log)。然后进行数据的修改。
如果出现了错误或者用户执行了 Rollback 语句,系统可以利用 Undo Log 中的备份将数据恢复到事务开始之前的状态。
和 Undo Log 相反,Redo Log 记录的是新数据的备份。在事务提交前,只要将 Redo Log 持久化即可,不需要将数据持久化。
当系统崩溃时,虽然数据没有持久化,但是 Redo Log 已经持久化。系统可以根据 Redo Log 的内容,将所有数据恢复到最新的状态。对具体实现过程有兴趣的同学可以去自行搜索扩展。
分布式事务解决方案
什么是分布式事务?
简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上说,分布式事务就是为了保证不同数据库的数据一致性。
分布式事务产生的原因
服务化,随着服务化,出现各个微服务,以及这些服务对应的库表,多个库表之间的数据操作 可能需要保证原子性。
CAP定理
CAP定理,又被叫作布鲁尔定理。对于设计分布式系统来说(不仅仅是分布式事务)的架构师来说,CAP就是你的入门理论。
CAP理论告诉我们,一个分布式系统不可能同时满足一致性(C:Consistency)、可用性(A:Availability)和分区容错性(P:Partion tolerance)这三个基本需求,最多只能同时满足其中的两项。
C (一致性):
在分布式环境下,一致性是指数据在多个副本之间是否能够保持一致的特性。对某个指定的客户端来说,读操作能返回最新的写操作。对于数据分布在不同节点上的数据上来说,如果在某个节点更新了数据,那么在其他节点如果都能读取到这个最新的数据,那么就称为强一致,如果有某个节点没有读取到,那就是分布式不一致。
A (可用性):
可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回正确结果。非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。可用性的两个关键一个是合理的时间,一个是合理的响应。合理的时间指的是请求不能无限被阻塞,应该在合理的时间给出返回。合理的响应指的是系统应该明确返回结果并且结果是正确的,这里的正确指的是比如应该返回50,而不是返回40。
P (分区容错性):
分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。打个比方,这里个集群有多台机器,有台机器网络出现了问题,但是这个集群仍然可以正常工作。
网络分区是指在分布式系统中,不同的节点分布在不同的子网络(机房或异地网络等)中,由于一些特殊的原因导致这些子网络之间出现网络不连通的状况,但各个子网络的内部网络是正常的,从而导致整个系统的网络环境被切分成了若干孤立的区域。需要注意的是,组成一个分布式系统的每个节点的加入与退出都可以看作是一个特殊的网络分区。
熟悉CAP的人都知道,三者不能共有,如果感兴趣可以搜索CAP的证明,在分布式系统中,网络无法100%可靠,分区其实是一个必然现象,如果我们选择了CA而放弃了P,那么当发生分区现象时,为了保证一致性,这个时候必须拒绝请求,但是A又不允许,所以分布式系统理论上不可能选择CA架构,只能选择CP或者AP架构。
对于CP来说,放弃可用性,追求一致性和分区容错性,我们的zookeeper其实就是追求的强一致。
对于AP来说,放弃一致性(这里说的一致性是强一致性),追求分区容错性和可用性,这是很多分布式系统设计时的选择,后面的BASE也是根据AP来扩展。
顺便一提,CAP理论中是忽略网络延迟,也就是当事务提交时,从节点A复制到节点B,但是在现实中这个是明显不可能的,所以总会有一定的时间是不一致。同时CAP中选择两个,比如你选择了CP,并不是叫你放弃A。因为P出现的概率实在是太小了,大部分的时间你仍然需要保证CA。就算分区出现了你也要为后来的A做准备,比如通过一些日志的手段,是其他机器回复至可用。
CAP是一个已经被证实的理论:一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容忍性(Partition tolerance)这三项中的两项。它可以作为我们进行架构设计、技术选型的考量标准。对于多数大型互联网应用的场景,结点众多、部署分散,而且现在的集群规模越来越大,所以节点故障、网络故障是常态,而且要保证服务可用性达到N个9(99.99..%),并要达到良好的响应性能来提高用户体验,因此一般都会做出如下选择:保证P和A,舍弃C强一致,保证最终一致性。
BASE理论
BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent (最终一致性)三个短语的缩写。BASE理论是对CAP中AP的一个扩展,通过牺牲强一致性来获得可用性,当出现故障允许部分不可用但要保证核心功能可用,允许数据在一段时间内是不一致的,但最终达到一致状态。满足BASE理论的事务,我们称之为“柔性事务”。
BASE理论指的是:
- Basically Available(基本可用):允许响应时间拉长,允许功能上的损失,允许降级页面(系统繁忙,稍后重试等),即分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。如,电商网站交易付款出
现问题了,商品依然可以正常浏览。 - Soft state(软状态):是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性。如订单的"支付中"、“数据同步中”等状态,待数据最终一致后状态改为“成功”状态。
- Eventually consistent(最终一致性):本质就是需要保证最终数据能够达到一致性,而不需要实时保证系统数据的强一致性。如订单的"支付中"状态,最终会变
为“支付成功”或者"支付失败",使订单状态与实际交易结果达成一致,但需要一定时间的延迟、等待。
两阶段提交
阶段一 提交事务请求
- 事务询问:
协调者向所有的参与者发送事务内容,询问是否可以执行事务提交操作,并开始等待各参与者的响应。 - 执行事务:
各参与者节点执行事务操作,并将Undo和Redo信息记录事务日志中。 - 各参与者向协调者反馈事务询问的响应:
如果参与者成功执行了事务操作,那么就反馈给协调者Yes响应,表示事务可以执行;如果参与者没有成功执行事务,那么就反馈给协调者No响应,表示事务不可以执行。
上面的过程在形式上近似是协调者组织各参与者对一次事务操作的投票表态过程,因此这个阶段也被称为“投票阶段”,即各参与者投票表明是否要继续执行接下去的事务提交操作。
阶段二 执行事务提交
在阶段二中,协调者会根据各参与者的反馈情况来决定最终是否可以进行事务提交操作,正常情况下,包含以下两种可能:
执行事务提交:
假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务提交。
- 发送提交请求:协调者向所有参与者节点发出Commit请求。
- 事务提交:参与者接收到Commit请求后,会正式执行事务提交操作,并在完成提交之后释放在整个事务执行期间占用的事务资源。
- 反馈事务提交结果:参与者在完成事务提交之后,向协调者发送ACK消息。
- 完成事务:协调者接收到所有参与者反馈的Ack消息后,完成事务。
中断事务
假如任何一个参与者向协调者反馈了No响应,或者在等待超时之后,协调者尚无法接收到所有参与者的反馈响应,那么就会中断事务。
- 发送回滚请求:协调者向所有参与者阶段发出Rollback请求。
- 执行事务回滚:参与者接收到Rollback请求后,会利用其在阶段一中记录的Undo信息来执行事务回滚操作,并在完成回滚之后释放在整个事务执行期间占用的资源。
- 反馈事务回滚结果:参与者在完成事务回滚之后,向协调者发送Ack消息。
- 完成事务中断:协调者接收到所有参与者反馈的Ack消息后,完成事务中断。
两阶段提交将一个事务的处理过程分为了投票和执行两个阶段,其核心是对每个事务都采用先尝试后提交的处理方式。
优缺点
两阶段提交协议的优点:原理简单,实现方便。
两阶段提交协议的缺点:
- 同步阻塞:在两阶段提交的执行过程中,所有参与该事务操作的逻辑都处于阻塞状态,也就是说,各个参与者在等待其他参与者响应的过程中,将无法进行任何其他操作。
- 单点问题:协调者单点问题。
- 数据不一致:在两阶段提交协议的阶段二,即执行事务提交的时候,当协调者向所有的参与者发送Commit请求之后。发送了局部网络异常或者是协调者在尚未发送完Commit请求之前自身发生了崩溃,导致最终只有部门参与者收到了Commit请求。于是,这部分收到了Commit请求的参与者就会进行事务的提交,而其它没有收到Commit请求的参与者则无法进行事务提交,于是整个分布式系统便出现了数据不一致性的问题。
- 太过保守:如果在协调者指示参与者进行事务提交询问的过程中,参与者出现故障而导致协调者始终无法获取到所有参与者的响应信息的话,这时协调者只能依靠自身的超时机制来判断是否需要中断事务,这样的策略显得比较保守。换句话说,两阶段提交协议没有设计较为完善的容错机制,任意一个节点的失败都会导致整个事务的失败。
三阶段提交
协议说明
三阶段提交,将两阶段提交协议的“提交事务内容”过程一分为二,形成了由CanCommit、PreCommit和DoCommit三个阶段组成的事务处理协议。
阶段一
- 事务询问:协调者向所有的参与者发送一个包含事务内容的canCommit请求,询问是否可以执行事务提交操作,并开始等待各参与者的响应。
- 等待反馈:各参与者在接收到来自协调者的canCommit请求后,正常情况下,如果其自身认为可以顺序执行事务,那么会反馈Yes响应,并进入预备状态,否则反馈No响应。
阶段二:PreCommit
在阶段二中,协调者会根据各参与者的反馈情况来决定是否可以进行事务的PreCommit操作,正常情况下,包含两种可能。
执行事务预提交:
假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务预提交。
- 发送事务预提交请求:协调者向所有参与者节点发出PreCommit的请求,并进入Prepared阶段。
- 执行事务预提交操作:参与者接收到preCommit请求后,会执行事务操作,并将Undo和Redo信息记录到事务日志中。(执行但不提交)
- 反馈事务执行的响应:如果参与者成功执行了事务操作,那么就会反馈给协调者Ack响应,同时等待最终的指令:提交(commit)或中止(abort)。
中断事务:
假如任何一个参与者向协调者反馈了No响应,或者在等待超时之后,协调者尚无法接收到所有参与者的反馈响应,那么就会中断事务。
- 发送中断请求:协调者向所有参与者节点发出abort请求。
- 无论是收到来自协调者的abort请求,或者是在等待协调者发送请求过程中出现超时,参与者都会中断事务。
阶段三:doCommit
该阶段将进行真正的事务提交,会存在以下两种可能的情况。
执行提交
- 发送提交请求:进入这一阶段,假设协调者处于正常工作状态,并且它接收到了来自所有参与者的Ack响应,那么它将从“预提交”状态转换到“提交”状态,并向所有的参与者发送doCommit请求。
- 执行事务操作:参与者接收到doCommit请求后,会正式执行事务提交操作,并在完成提交之后释放在整个事务执行期间占用的事务资源。
- 反馈事务提交结果:参与者在完成事务提交之后,向协调者发送Ack消息。
- 完成事务:协调者接收到所有参与者反馈的Ack消息之后,完成事务。
中断事务:
如果有任意一个参与者向协调者反馈了No响应,或者在等待超时之后,协调者尚无法接收到所有参与者的反馈响应,那么就会中断事务。
- 发送中断请求:协调者向所有的参与者节点发送abort请求。
- 事务回滚:参与者接收到abort请求后,会利用其在阶段二中记录的Undo信息来执行事务回滚操作,并在完成回滚之后释放在整个事务执行期间占用的资源。
- 反馈事务回滚结果:参与者在完成事务回滚之后,向协调者发送Ack消息。
- 中断事务:协调者接收到所有参与者反馈的Ack消息后,中断事务。
需要注意的是,一旦进入阶段三,可能会存在以下两种故障:
- 协调者出现问题。
- 协调者和参与者之间的网络出现故障。
无论出现那种情况,最终都会导致参与者无法及时接收到来自协调者的doCommit或是abort请求,针对于这样的异常情况,参与者都会在等待超时之后,继续进行事务提交。(乐观态度,认为前面已经进行过PreCommit请求了,事务一定能执行成功)。
优缺点:
三阶段提交协议的优点:相较于两阶段提交协议,三阶段提交协议最大的优点是降低了参与者的阻塞范围,并且能够在出现单点故障后继续达成一致。
三阶段提交协议的缺点:三阶段提交协议在去除阻塞的同时也引入了新的问题,那就是在参与者接收到preCommit消息后,如果网络出现分区,此时协调者所在的节点和部分参与者无法进行正常的网络通信,在这种情况下,该参与者依然会进行事务的提交,这必然出现数据的不一致性。
ZooKeeper的ZAB协议
ZooKeeper并没有完全采用Paxos算法,而是使用了一种称为ZooKeeper Atomic Broadcast(ZAB,ZooKeeper原子消息广播协议)的协议作为其数据一致性的核心算法。
ZAB协议是为分布式协调服务ZooKeeper专门设计的一种支持崩溃恢复的原子广播协议。
ZAB并不是分布式事务的解决方案,而是分布式(主从)各副本之间的数据一致性解决方案。
ZAB的核心是定义了对于那些会改变ZooKeeper服务器数据状态的事务请求的处理方式。
消息广播
ZooKeeper只允许唯一的一个Leader服务器来接收并处理客户端的所有事务请求,非Leader服务器即使接收到事务请求,也会转发给Leader服务器。
所有事务请求必须由一个全局唯一的服务器来协调处理,这样的服务器被称为Leader服务器,而余下的其他服务器则成为Follower服务器。Leader服务器负责将一个客户端事务请求转换成一个事务Proposal(提议),并将该Proposal分发给集群中所有的Follower服务器。之后Leader服务器需要等待所有Follower服务器的反馈,一旦超过半数的Follower服务器进行了正确的反馈后,那么Leader就会再次向所有的Follower服务器分发Commit消息,要求其将前一个Proposal进行提交。
这是一个类似于两阶段提交的过程,但是需要注意此处是只要求有超过半数的Follower服务器进行了正确的反馈即提交事务;并且在ZAB协议的提交过程中,移除了中断逻辑,所有的Follower服务器要么正常反馈Leader提出的事务Proposal,要么抛弃Leader服务器。
消息顺序性的处理
在整个消息广播中,Leader服务器会为每个事务请求生成对应的Proposal来进行广播,并且在广播事务Proposal之前,Leader服务器会首先为这个事务Proposal分配一个全局单调递增的唯一ID,我们称之为事务ID(ZXID)。由于ZAB协议需要保证每一个消息严格的因果关系,因此必须将每一个Proposal按照其ZXID的先后顺序来进行排序和处理。
这个ZXID是一个64位的数字,其中低32位可以看作是一个简单的单调递增的计数器,针对客户端的每一个事务请求,Leader服务器在产生一个新的事务Proposal的时候,都会对该计数器进行加1操作。而高32位则代表了Leader周期epoch的编号,每当选举产生一个新的Leader服务器,就会从这个Leader服务器上取出其本地日志中最大事务Proposal的ZXID,并从该ZXID中解析出对于的epoch值,然后再对其进行加1操作,之后就会以此编号作为新的epoch,并将低32位置0来开始生成新的ZXID。
具体的保证顺序性的解决办法就是,在消息广播过程中,Leader服务器会为每一个Follower服务器都各自分配一个单独的队列,然后将需要广播的事务Proposal依次放入这些队列中,并且根据FIFO策略来进行消息发送。每一个Follower服务器在接收到这个事务Proposal之后,都会首先将其以事务日志的形式写入到本地磁盘中去,并且在成功写入后反馈给Leader服务器一个Ack响应。当Leader服务器接收到超过半数Follower的Ack响应之后,就会广播一个Commit消息给所有的Follower服务器以通知其进行事务提交,同时Leader自身也会完成对事务的提交,而每一个Follower服务器在接收到Commit消息后,也会完成对事务的提交。
崩溃恢复(Leader选举)
一旦Leader服务器出现崩溃,或者说由于网络原因导致Leader服务器失去了与过半Follower的联系,那么就会进入崩溃恢复模式。此时,ZK集群将会发起一轮Leader选举,选举规则是:优先选事务ID大的,在事务Id相同的情况下,优先选服务器编号大的。
第一轮每台服务器都选择自己,并进行投票广播;同时也接收来自其他服务器的投票信息,并拿接收到的投票中事务ID和服务器编号与自己的投票对比,谁高就选谁,然后再次广播投票。 每次接收到投票,都会计算自己接收到选票是否能判断某台机器已经有超过半数的选票,一旦感知到这种情况,就会更改自己的状态。 这样选举出来的Leader一定拥有集群中最大的事务ID,也就是数据最全的那台机器。
因为是事务Id最大的机器,那个这个新选举出来的leader一定具有所有已提交的提案。更为重要的是,如果让具有最高事务Id的机器来成为Leader,就可以省去Leader服务器检查Proposal的提交和丢失工作这一步操作了。 这里说的提交是指:ZAB协议需要确保那些已经在Leader服务器上提交的事务最终被所有服务器提交。这里说的丢弃是指:ZAB协议需要确保丢弃那些只在Leader服务器上被提出的事务。
数据同步
在完成Leader选举之后,Leader服务器会检查事务日志中的所有已经提交的Proposal是否都已经被集群中过半的机器提交了,即是否完成数据同步。数据同步过程如下:
Leader服务器会为每一个Follower服务器都准备一个队列,并将那些没有被各个Follower服务器同步的事务以Proposal消息的形式逐个发送给Follower服务器,并在每一个Proposal消息后面紧接着再发送一个Commit消息,以表示该事务已经被提交。等到Follower服务器将所有其尚未同步的事务Proposal都从Leader服务器上同步过来并成功应用到本地数据库中后,Leader服务器就会将该Follower服务器加入到真正可用的Follower列表中,并开始之后的其他流程。
分布式事务解决方案:
原则上尽量避免分布式事务,保证强一致性的成本远比快速发现不一致并修复的成本高。
补偿事务机制,保证最终一致性
事务补偿就是指在事务链中的任何一个正向事务操作,都必须存在一个完全符合回滚规则的可逆事务。如果是一个完整的事务链,则必须保证事务链中的每一个业务操作都有对应的可逆服务。
比如提现操作,需要经历账户余额扣除,第三方真正打款等多个操作。此时,如果使用补偿操作流程如下:
- 用户发起提现,扣除用户账户余额,生成提现流水。
- 提交给第三方。
- 第三方尝试打款,无论成功与失败都会通知发起方。
- 发起方收到通知后,如果发现提现失败,就尝试给用户生成一条提现失败返回流水。将之前扣减的钱返回去。
TCC(try Confirm Cancel)
TCC其实也就是采用的补偿机制,它分为三个阶段:
- Try阶段主要是对业务系统做检测及资源预留。
- Confirm阶段主要是对业务操作做确认提交,
- Cancel阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消操作,释放预留资源。
以会员卡扣款或余额消费或优惠券使用 冻结/占用-使用-核销流程为例:
- try:检查账户余额是否充足,充足的话就冻结(相当于预留出的资源,也可对多个资源进行冻结)
- confirm:执行整个分布式事务操作(相当于只利用try预留出的资源)。
- cancel:恢复try阶段预留的资源。(若在某一个阶段失败,则将try预留的资源恢复try前的状态,理解为把余额给加回去)
再以在线下单为例,Try阶段会去扣库存,Confirm阶段则是去更新订单状态,如果更新订单失败,则进入Cancel阶段,会去恢复库存。
总之,TCC就是通过代码人为实现了两阶段提交,不同的业务场景所写的代码都不一样,复杂度也不一样,因此,这种模式并不能很好地被复用。
基于消息的分布式事务
这类事务机制将分布式事务分成多个本地事务,这里称之为主事务与从事务。首先主事务本地进行提交,然后使用消息通知从事务,从事务从消息中获取事务操作关键信息进行本地操作提交。可以看出这是一个异步事务机制、只能保证最终一致性;但可用性非常高,不会因为故障而发生阻塞。另外,主事务已经先行提交,如果因为从事务无法提交,要回滚主事务还是比较麻烦,所以这种模式只适用于理论上大概率成功的业务情况,即从事务的提交失败可能是由于故障,而不大可能是逻辑错误。
基于异步消息的事务机制主要有两种方式:本地消息表与事务消息。二者的区别在于:怎么保证主事务的提交与消息发送这两个操作的原子性。
-
本地消息表
基于本地消息表的方案是指将消息写入本地数据库,通过本地事务保证主事务与消息写入的原子性。例如银行转账的例子,伪码如下:
begin transaction: update User set account = account - 100 where userId = 'A' ; insert into message(userId, amount, status) values('A', 100, 1) ; commit transaction
主事务将消息写入本地消息表后,从事务通过pull或者push模式,获取消息并执行。如果是push模式,那么一般使用具有持久化功能的消息队列,从事务订阅消息。如果是pull模式,那么从事务定时去拉取消息,然后执行。
-
事务消息
所谓的事务消息就是基于消息中间件的两阶段提交,本质上是对消息中间件的一种特殊利用,它是将本地事务和发送消息放在一个分布式事务里,保证要么本地操作成功并且对外发送消息成功,要么二者都失败,开源的RocketMQ就支持这一特性(最新版已经不再支持了),下面我们以RocketMQ来分析其具体原理:
- RocketMQ第一阶段发送Prepared消息时,会拿到消息的地址。
- 第二阶段执行本地事务。
- 第三阶段根据第二阶段执行的结果通过第一阶段拿到的地址去访问消息,并修改消息状态。如果此时确认消息发送失败了,RocketMQ会定期扫描消息集群中的事务消息,如果发现了Prepared消息,就会向消息发送方确认(确认的方式 是强制在第一阶段注册一个回调接口,此时对该回调接口进行调用,获得执行结果。),以决定事务消息状态。
基于消息中间件的两阶段提交往往用在高并发场景下,将一个分布式事务拆分成一个消息事务(A系统的本地操作 + 发消息) + B系统的本地操作,其中B系统的操作由消息驱动,只要事务消息成功,那么A操作一定成功,消息也一定发出来了,这时候B收到消息去执行本地操作,如果本地操作失败,消息会重投,直到B操作成功,这样就变相地实现了A与B的分布式事务。如果更完善的话,考虑B一直重试失败情况,还可以提供一个A操作的回滚机制。整个过程原理如下:
对账(最鲁棒的技术)
实时对账、准实时对账、T+1的离线对账。对不平,自动冲正,自动轧差。常见于交易结算等财务系统中。
最大努力送达型
- 允许中间状态出现,比如提现处理中,同时记录失败记录表,定时Job重试。
- 使用MQ延迟消息重试。