事务
单机事务
所谓单机事务
指的就是事务,它具有原子性(Atomicity)
、一致性(Consistency)
、隔离性(Isolation)
和持久性(Durability/Permanence)
。
执行事务的时候,一般以BEGIN
开头,以END
结尾,中间要么COMMIT TRANSACTION
,要么ROLLBACK TRANSACTION
。
下面是实现事务的SQL
伪代码。
> START TRANSACTION;
-- 汇总错误次数
> DECLARE @errorSum INT;
> UPDATE t_account SET money = money - 100 WHERE username = 'lixingyun';
> SET @errorSum = @errorSum + @@error;
> INSERT INTO t_order(id, productId, userId, number, price) VALUES(21885463287451214, 1, 1, 2, 50.00);
> SET @errorSum = @errorSum + @@error;
> INSERT INTO t_order_details(id, orderId, ......) VALUES(1, 21885463287451214, ......);
> SET @errorSum = @errorSum + @@error;
> INSERT INTO t_order_details(id, orderId, ......) VALUES(2, 21885463287451214, ......);
> SET @errorSum = @errorSum + @@error;
> UPDATE t_store SET number = number - 2 WHERE productId = 1;
> SET @errorSum = @errorSum + @@error;
-- 如果有错误就回滚
> IF @errorSum <> 0
> print '交易失败,回滚事务';
> ROLLBACK TRANSACTION;
-- 否则就提交
> ELSE
> print '交易成功,提交事务';
> COMMIT TRANSACTION;
> END TRANSACTION;
分布式事务
在分布式系统出现以后,也出现了分布式事务问题,它其实非常像一个本地数据库的放大版
,但又有所不同。
定理一:CAP
CAP定理又叫布鲁尔定理(Brewer's theorem)
,它是Consistency(一致性)
、Availability(可用性)
和Partition Tolerance(分区容错度)
这三个首字母的缩写。

CAP定理有两个非常明确的界定。
一是它认为任何分布式系统都不可能同时满足
Consistency
、Availability
和Partition Tolerance
这三者中的全部。二是
C
、A
和P
这三者中,只能首先满足P
,然后满足A
,至于C
,则通过以时间换空间
的方式实现,这是因为C
是可以被分为三个不同的层次的。强一致性
:在任意时刻,所有不同的分布式节点中的数据完全一致,不容许存在任何不一致的情况。这种一致性需要非常强力的保证机制,其结果就是可能会影响到系统运行时的性能与响应速度,不过在金融、证券、医疗、航空航天等关乎国计民生的行业中,这种强一致性
是必须的。弱一致性
:当某个分布式节点中的数据被更新后,后续对该数据的读取操作可能得到更新后的值,也可能是更改前的值。这种弱一致性既没有机制上的保障,也没有时间上的保证,即不知道也不保证多久后数据将被更新为正确的数值。最终一致性
:分布式系统不保证在任意时刻任意节点上的同一份数据都是相同的,但是随着时间的迁移,不同节点上的同一份数据总是在向趋同的方向变化,即所有数据哪怕当前有错误、遗漏或混乱,但只要在系统不停止运行的前提下,它总能够更新到正确的数值。
上面所说的时间换空间
的方式就是指的最终一致性。
定理二:BASE
BASE定理是CAP定理的延续和扩展,它的核心思想简单来说就是:如果无法做到强一致,那么每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。

BA(Basic Available)基本可用
:分布式系统可以通过适当延长响应时间,或者返回一个性能降级页面,从而缓解服务器压力。例如当举行大促(秒杀)之类的活动时,有些用户访问网站时间变长,或者得到服务繁忙
的响应页面,这就是典型的性能降级措施。S(Soft State)柔性状态
:指允许分布式系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统整体的稳定性与可用性。即允许系统不同节点之间的数据存在不一致,并允许数据同步的过程存在延时,这其实就是CAP定理中的弱一致性
层次。E(Eventual Consisstency)最终一致性
:分布式系统中同一数据不同副本的状态,可以不必时刻保持一致,但一定要保证在经过一段时间后是一致的,这也是CAP定理中的最终一致性
。
CAP定理和BASE定理本质上就是同一个意思:以时间换空间
。

2PC
2PC的全称是Two Phase Commit Protocol
,即两阶段提交协议
,两阶段
指的是准备阶段
和提交阶段
。
在2PC中有两种角色:事务协调者
和事务参与者
。
事务协调者
也称为事务管理器TM(Transaction Manager)
,通常一个分布式系统中只有一个,而事务参与者
也称为资源管理器RM(Resource Manager)
,通常一个分布式系统中也只有一个。
准备阶段
的执行过程如下图所示。

事务协调者TM
给每个事务参与者RM
发送Prepare
消息,询问是否已准备好。每个
事务参与者RM
要么直接返回失败(如权限验证失败),要么在本地开始执行事务操作,写本地的redo
和undo
日志,但不提交事务。如果
事务参与者RM
操作成功,则给事务协调者TM
返回成功,否则返回失败。
而在提交阶段
的执行过程如下图所示。

整个2PC的事务执行过程类似一场运动比赛,裁判就是事务协调者TM
,而运动员们就是事务参与者RM
。
2PC的优势在于:尽可能地保证了数据的强一致性,它非常适合对数据的强一致性要求很高的业务场景。但不足也很明显:性能严重拖后腿、实现复杂,所以不适合具有海量终端用户的高并发、高TPS、大吞吐量的分布式应用。
3PC
3PC全称Three Phase Commit Protocol
,即三阶段提交协议
。
为了提升性能,3PC在
事务协调者TM
和事务参与者RM
中都引入了超时机制。为了提升可靠性,它在2PC的准备阶段和提交阶段之间插入了一个预提交阶段,保证在最后提交阶段之前各参与节点的状态是一致的,其实就是再多确认一遍,防止出错。

上面的第1 ~ 3
步表示canCommit
阶段,第4 ~ 6
步表示preCommit
阶段,而第7 ~ 9
步则代表doCommit
阶段。
第一阶段(canCommit)
:事务协调者TM
给所有的事务参与者RM
发起canCommit
请求,事务参与者RM
收到请求后,根据自己的情况判断是否可以执行提交。如果可以则返回OK
,否者返回FAIL
,且执行时不开启本地事务。第二阶段(preCommit)
:事务协调者TM
向所有事务参与者RM
发起preCommit
请求,事务参与者RM
收到请求后,开启本地事务并执行,但执行完后并不提交事务,这和2PC中的第一阶段是一致的。第三阶段(doCommit)
:此时与两阶段中的第二阶段一致,向所有事务参与者RM
发起提交事务的请求,事务参与者RM
收到通知,或者提交事务,或者超时,并将事务提交的结果反馈给事务协调者TM
。
相较于2PC,3PC的改进无非就是多了一个确认过程,而且整体的性能提升并不明显,所以同样没能得以普及。
除了3PC,2PC还衍生出了另外一种也有三个阶段
的分布式事务解决方案:TCC。
TCC
TCC是一种从2PC发展出来的相对比较成熟的分布式事务解决方案。
它的全称是Try-Confirm-Cancel
,它是由Try
、Confirm
、Cancel
这三个部分组成,并且都要由工程师手动编码实现。
TCC本质上属于一种柔性事务,由于它需要与具体业务耦合,所以侵入性很强。它的一般实现过程如下图所示。

所有事务参与者都需要实现
try
,confirm
,cancel
这三个方法。事务发起方向事务协调者发起事务请求,事务协调者调用所有事务参与者的
try
方法完成资源的预留。如果事务协调者发现有事务参与者的
try
方法在预留资源时失败,则调用事务参与者的cancel
方法回滚,但cancel
方法需要实现业务幂等
。如果事务协调者发现所有事务参与者的
try
方法返回都OK,则事务协调者调用所有参与者的confirm
方法。这时不做资源检查,直接提交。如果事务协调者发现所有事务参与者的
confirm
方法都成功了,则分布式事务结束。如果事务协调者发现有些事务参与者的
confirm
方法失败了,或者由于网络原因没有收到回执,则会进行重试。如果重试一定次数后还是失败就做事务补偿。
TCC的侵入性极强,而且由于TCC的操作本身分为两个阶段,因此所有的业务代码都需要做相应的改造,这一点在选用之前务必要搞清楚。
另外,由于网络或事务本身的异常可能会导致try
、confirm
和cancel
这三个方法被执行多次,所以它们也必须要实现接口的幂等性。
本地消息表
由于2PC及其衍生出来的3PC和TCC始终带有强一致性的事务约束,所以后来eBay公司提出了一种和2PC思想完全不同的分布式事务解决方案:本地消息表
,其核心思路是将需要分布式处理的任务通过事务消息的方式来异步执行。
事务消息可以存储到本地文本、SQL
、NoSQL或消息队列中,然后再通过业务规则自动地或人工地发起重试,直到事务被成功执行,或达到失败上限后回滚。需要注意的是,本地消息表
中的本地
是相对于分布式环境而言的,它是一个逻辑上的概念,而非绝对物理上的概念。
此方案在实际实现时,绝大多数都是要么将事务消息保存在消息队列的事务消息队列中,要么将它保存在独立的NoSQL中。
以订单支付系统为例,老式的本地消息表的实现过程如下图所示。

作为事务发起方,订单服务在保存订单数据时,也保存一条消息记录到消息表中。
订单服务通过消息中间件给支付组件发送订单相关消息。
支付组件将收到的消息保存在事务自己的消息表中,同时将支付完毕的订单数据通过消息队列再发送给订单服务。
订单服务收到支付组件发来的业务处理消息,同时将之前状态为
未支付
的消息,改为已支付
状态。
但早期的消息表方案有几个明显的问题。
事务发起方和事务被调用方使用不同的消息表,给操作上带来极大不便,而且也会带来性能上的损失,其实完全没有必要分成两部分。
有些本地消息表的实现方案要求保存业务数据和保存消息数据必须在同一个本地事务中,这也完全没必要。因为消息有可能会保存在NoSQL中,而NoSQL是不太可能有事务的。
没有对消息投递失败的情况作出处理,这有可能会造成回滚记录缺失或两边的事务消息数据不一致的问题,也就是有订单支付异常记录,但却没有对应的消息可查、可回滚。
有鉴于此,在本地消息表的基础上产生改良方案:可靠消息最终一致性
。
可靠消息最终一致性
本地消息表方案不同的是,可靠消息最终一致性
方案不仅将事务主动方消息表和事务被动方消息表合二为一,也不仅将本地消息表设立为独立消息服务,而且还通过定时读取消息表这种补偿
的方式确保了数据的一致性。这是一种基于柔性事务的最终一致性,它允许存在中间状态,但一段时间后,能够达到数据的最终一致性状态即视为事务成功完成。

业务逻辑的处理服务在事务提交前,先封装消息。
待处理完若干业务后,消息先存储然后再通过MQ发送。
消费者收到消息后,就启动相应的任务,并放到定时任务线程池中执行。
如果业务操作成功完成,就删除预先保存的消息。
如果业务处理状态不明确(失败或超时),那么独立的消息系统会每隔一段指定的时间就把所有存储的消息都重发一遍,直到与消息相关的业务被成功执行为止。
可消息最终一致性的实现关键点在于两点。
事务消息的存储。为实现这个目标,可以采用MongoDB、Redis、Cassandra、Elasticsearch等NoSQL,然后使用定时任务定期读取所有已保存的且为
发送中
的消息。消息的可靠投递。不同的消息队列中间件有不同的方式来确保消息的可靠投递。例如,RocketMQ使用事务、半消息、消息持久化、同步双写、重试机制等方式来实现消息投递的可靠性。
Saga
虽然2PC及其衍生出来的3PC、TCC、消息表等方式可以保证实现事务的最终一致性,但对于某些分布式事务场景,如流程多、时间长,且还会涉及到调用外部不可控的接口服务时,之前的那些解决方案就有些力不从心了。尤其是体现在以金融为核心业务的应用中,一个流程下来,涉及到的利益相关方可能多达十几个,接口调用更是完全不可控。有鉴于此,1987年普林斯顿大学的Hector Garcia-Molina
和Kenneth Salem
两人联名共同发表了一篇针对这类长事务(Long Lived Transaction)
的论文。
论文的核心思想是将长事务
,也就是分布于多个不同系统中的全部事务视为一个整体,然后再将它们拆分为一个一个的本地短事务
,由长事务
来统一协调,就好像这些事务是处于一个大的虚拟的逻辑的数据库中一样。
他们把这种复杂且不可控的长事务
命名为Saga,并且定义了Saga的两个核心属性。
每个Saga都由一系列
有序
的子事务(Sub-Transaction)Ti
组成。每个
Ti
都有对应的补偿动作Ci
,补偿作用于撤销Ti所造成的结果。
例如,锁定并扣减库存是T1、创建订单是T2、支付服务是T3,那么针对每个Ti
都对应一个补偿动作Ci
,即为回复并解锁库存C1、撤销订单C2、返还支付金额C3。
Saga有两种恢复策略。
前向恢复(forward recovery)
,也叫做勇往直前
,就是执行到底,不成功就会一直尝试,这种方式适用于必须要成功的场景。

后向恢复(backward recovery)
,也叫做一退到底
,在执行事务失败时,补偿所有已完成的事务,而且补偿时是按照Ti执行的倒序进行的。例如,先补偿C3,再C2,最后补偿C1。

在分布式协调方面,Saga也采用了两种模式。
编排(Choreography)方式
:通过事件让各个子事务之间互相调用、分配、决策和排序,这是一种去中心化的模式。

控制(Orchestration)方式
:通过中心控制类来统一调用和接受服务的反馈,处理事务的执行和回滚步骤。

这两种方式的区别简单来说就是有Saga控制器和没有Saga控制器的区别。
作为一种分布式事务解决方案,Saga已经成为开源的Seata分布式事务框架所支持的四大事务模式之一。
Seata
除了CAP定理和BASE理论这两大理论性框架,分布式事务还有若干种解决方案,包括2PC、3PC、TCC、本地消息表
、可靠消息最终一致性
、Seata等。
尤其是Seata,它是目前为止分布式事务中比较完备的一种解决方案,也可以说是集大成者。
Seata是Simple Extensible Autonomous Transaction Architecture
这几个首字母的简写 ,用官网的话来说,它是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务
,意为简单可扩展的自主事务架构
,该项目的目标是让分布式事务的使用像本地事务的使用一样,简单和高效,并逐步解决开发者们遇到的分布式事务方面的所有难题
,相当于从互联网的层面重写数据库事务引擎。
Seata主要由三个部分组成。
TC
:Transaction Coordinator事务协调器
,负责维护全局和分支事务的状态,驱动全局性事务的提交或回滚。TM
:Transaction Manager事务管理者
,它定义全局事务的范围,开始、提交或回滚全局事务。类似于2PC中的事务协调者。RM
:Resource Manager资源管理者
,它用于管理分支事务上的资源,并且向TC
注册分支事务,上报分支事务的状态,接收TC
的命令来提交或者回滚分支事务,类似于2PC中的事务参与者。
拿订单、支付和库存来举例。在微服务架构下,它们分别代表了三个微服务,通过RPC实现接口调用。

如果它们的分布式事务交由Seata来完成的话,那么就会是下面这样的。

业务调用方作为事务入口,例如,
Service
层或Controller
层。程序代码中会通过Seata的全局事务注解@GlobalTransactional
来指明它是一个全局事务,这时它的角色为事务管理者TM
并与作为事务协调器TC
的Seata交互。然后,事务管理者TM
向事务协调器TC
发送请求,说明程序即将开启一个全局事务,事务协调器TC
生成并返回业务调用方一个全局唯一的事务ID,即XID
。业务调用方得到XID
后,将它作为参数,开始调用微服务,例如支付服务Payment。当Payment收到
XID
时就知道自己的事务是全局事务的一部分。Payment执行业务逻辑并操作完数据库后,就会把自己的事务注册到事务协调器TC
,作为这个XID
下面的一个分支事务,同时将执行结果(成功或失败)也返回给事务协调器TC
。注意,这里只是执行成功了,但事务并未提交,此时Payment的角色为资源管理者RM
,这里的资源指的就是本地数据库。另外两个微服务Order、Inventory的事务执行逻辑与Payment是一样的。在各个微服务都执行完成之后,
事务协调器TC
就知道了XID
下各个分支事务的执行结果,事务管理者TM
和业务调用方也会一并知晓。事务管理者TM
如果发现各个微服务的分支事务都执行成功的话,就会请求事务协调器TC
提交这个XID
,否则回滚。事务协调器TC
收到业务调用方的请求后,向XID
下所有的分支事务发起相应请求。各个微服务收到请求后执行相应指令,并把结果上报给事务协调器TC
。
Seata从理论上面来说,吸收和借鉴了之前的2PC及其衍生出来的3PC和TCC等方案中的优点,并对其不足做了相应改进。

因此,Seata支持四种模式。
官方提供的资源
以AT模式为例,它的实现过程如下。
准备环境
使用已有的环境:Docker部署MySQL。
官方用多种方式实现了AT模式,例如Dubbo方式、Spring Boot方式和Spring Boot Dubbo方式等,选择其中一种(这里选择Spring Boot方式)。
Clone
或下载代码,然后导入到IDEA。
服务配置
seata-server/conf/
里面包含了服务端和日志的所有相关配置。
application.example.yml
:Seata服务端配置的样例文件。application.raft.example.yml
:使用Raft事务存储模式的服务端配置样例文件。application.yml
:Seata的服务端配置文件,这个是真正用来配置服务端的。logback-spring.xml
:Logback日志配置文件。
可以按照需求修改seata-server/conf/application.yml
文件,也可以什么都不改,采用默认配置。
另外,application.yml
中的console.user
是SeataWeb UI
的默认用户名和密码。
修改代码
执行
src/main/resources/all.sql
脚本文件,创建客户端所需的业务表和系统表。如果
src/main/resources/registry.conf
的type = "file"
,那就要修改src/main/resources/file.conf
,将其中的default.grouplist
改为正确的Seata服务端IP
地址,端口号默认是8091
(就是官方说的7091 + 1000
)。修改
src/main/resources/application.properties
属性文件中的数据库连接配置。
启动服务
- 首先启动Seata服务端。
> cd seata-server/bin
# 启动服务
> sh ./seata-server.sh -p 8091 -h <服务器IP> -m file
# 服务启动后可以在/root/logs/seata/中查看启动日志
> tail -f /root/logs/seata/seata-server.8091.all.log
2023-06-22 21:37:02.034 INFO --- [ServerHandlerThread_1_2_500] [io.seata.server.coordinator.DefaultCoordinator] [doGlobalBegin] [172.16.185.176:8091:5260755181745868801]: Begin new global transaction applicationId: springboot-seata,transactionServiceGroup: my_test_tx_group, transactionName: spring-seata-tx,timeout:300000,xid:172.16.185.176:8091:5260755181745868801
2023-06-22 21:37:02.036 INFO --- [batchLoggerPrint_1_1] [io.seata.core.rpc.processor.server.BatchLogHandler] [run] []: result msg[single]: GlobalBeginResponse{xid='172.16.185.176:8091:5260755181745868801', extraData='null', resultCode=Success, msg='null'}, clientIp: 172.16.185.1, vgroup: my_test_tx_group
2023-06-22 21:37:02.657 INFO --- [batchLoggerPrint_1_1] [io.seata.core.rpc.processor.server.BatchLogHandler] [run] []: receive msg[merged]: BranchRegisterRequest{xid='172.16.185.176:8091:5260755181745868801', branchType=AT, resourceId='jdbc:mysql://172.16.185.176:3306/seata', lockKey='order_tbl:1', applicationData='{"skipCheckLock":true}'}, clientIp: 172.16.185.1, vgroup: my_test_tx_group
2023-06-22 21:37:02.669 INFO --- [ForkJoinPool.commonPool-worker-2] [io.seata.server.coordinator.AbstractCore] [lambda$branchRegister$0] [172.16.185.176:8091:5260755181745868801]: Register branch successfully, xid = 172.16.185.176:8091:5260755181745868801, branchId = 5260755181745868802, resourceId = jdbc:mysql://172.16.185.176:3306/seata ,lockKeys = order_tbl:1
2023-06-22 21:37:02.670 INFO --- [batchLoggerPrint_1_1] [io.seata.core.rpc.processor.server.BatchLogHandler] [run] []: result msg[merged]: BranchRegisterResponse{branchId=5260755181745868802, resultCode=Success, msg='null'}, clientIp: 172.16.185.1, vgroup: my_test_tx_group
2023-06-22 21:37:02.771 INFO --- [batchLoggerPrint_1_1] [io.seata.core.rpc.processor.server.BatchLogHandler] [run] []: receive msg[single]: GlobalCommitRequest{xid='172.16.185.176:8091:5260755181745868801', extraData='null'}, clientIp: 172.16.185.1, vgroup: my_test_tx_group
2023-06-22 21:37:02.777 INFO --- [batchLoggerPrint_1_1] [io.seata.core.rpc.processor.server.BatchLogHandler] [run] []: result msg[single]: GlobalCommitResponse{globalStatus=Committed, resultCode=Success, msg='null'}, clientIp: 172.16.185.1, vgroup: my_test_tx_group
2023-06-22 21:37:03.596 INFO --- [batchLoggerPrint_1_1] [io.seata.core.rpc.processor.server.BatchLogHandler] [run] []: receive msg[single]: BranchCommitResponse{xid='172.16.185.176:8091:5260755181745868801', branchId=5260755181745868802, branchStatus=PhaseTwo_Committed, resultCode=Success, msg='null'}, clientIp: 172.16.185.1, vgroup: my_test_tx_group
2023-06-22 21:37:03.597 INFO --- [AsyncCommitting_1_1] [io.seata.server.coordinator.DefaultCore] [lambda$doGlobalCommit$1] [172.16.185.176:8091:5260755181745868801]: Commit branch transaction successfully, xid = 172.16.185.176:8091:5260755181745868801 branchId = 5260755181745868802
2023-06-22 21:37:03.599 INFO --- [AsyncCommitting_1_1] [io.seata.server.coordinator.DefaultCore] [doGlobalCommit] [172.16.185.176:8091:5260755181745868801]: Committing global transaction is successfully done, xid = 172.16.185.176:8091:5260755181745868801.
在浏览器中访问http://服务器IP:7091/,输入在application.yml
中设置的用户名和密码,就可以看到Seata的Web UI
了。

- 然后启动Seata客户端代码,也就是运行Spring Boot中的
SpringbootSeataApplication
服务。
public static void main(String[] args) throws Exception {
SpringApplication.run(SpringbootSeataApplication.class, args);
BusinessService businessService = BEAN_FACTORY.getBean(BusinessService.class);
// 服务启动后立即在子线程中执行businessService.purchase()方法
Thread thread = new Thread(() -> businessService.purchase("U100001", "C00321", 2));
thread.start();
//keep run
Thread.currentThread().join();
}
执行结果如下。
2023-06-22 21:39:09.789 [Thread-1] INFO io.seata.tm.TransactionManagerHolder - TransactionManager Singleton io.seata.tm.DefaultTransactionManager@7fdc6c4f
2023-06-22 21:39:09.798 [Thread-1] INFO i.s.tm.api.DefaultGlobalTransaction - Begin new global transaction [172.16.185.176:8091:5260755181745868803]
2023-06-22 21:39:09.803 [Thread-1] INFO o.a.seata.service.BusinessService - purchase begin ... xid: 172.16.185.176:8091:5260755181745868803
2023-06-22 21:39:09.803 [Thread-1] INFO o.a.seata.service.StorageService - Stock Service Begin ... xid: 172.16.185.176:8091:5260755181745868803
2023-06-22 21:39:09.803 [Thread-1] INFO o.a.seata.service.StorageService - Deducting inventory SQL: update stock_tbl set count = count - 2 where commodity_code = C00321
2023-06-22 21:39:10.186 [Thread-1] INFO o.a.seata.service.StorageService - Stock Service End ...
2023-06-22 21:39:10.187 [Thread-1] INFO o.apache.seata.service.OrderService - Order Service Begin ... xid: 172.16.185.176:8091:5260755181745868803
2023-06-22 21:39:10.187 [Thread-1] INFO o.a.seata.service.AccountService - Account Service ... xid: 172.16.185.176:8091:5260755181745868803
2023-06-22 21:39:10.187 [Thread-1] INFO o.a.seata.service.AccountService - Deducting balance SQL: update account_tbl set money = money - 400 where user_id = U100001
2023-06-22 21:39:10.203 [Thread-1] INFO o.a.seata.service.AccountService - Account Service End ...
2023-06-22 21:39:10.204 [Thread-1] INFO o.apache.seata.service.OrderService - Order Service SQL: insert into order_tbl (user_id, commodity_code, count, money) values (U100001, C00321, 2, 400)
2023-06-22 21:39:10.296 [Thread-1] INFO io.seata.rm.AbstractResourceManager - branch register success, xid:172.16.185.176:8091:5260755181745868803, branchId:5260755181745868804, lockKeys:order_tbl:2
2023-06-22 21:39:10.304 [Thread-1] WARN i.s.c.l.EnhancedServiceLoader$InnerEnhancedServiceLoader - Load [io.seata.rm.datasource.undo.parser.ProtostuffUndoLogParser] class fail: io/protostuff/runtime/IdStrategy
2023-06-22 21:39:10.358 [Thread-1] INFO o.apache.seata.service.OrderService - Order Service End ... Created 2
2023-06-22 21:39:10.358 [Thread-1] INFO i.s.tm.api.DefaultGlobalTransaction - transaction 172.16.185.176:8091:5260755181745868803 will be commit
2023-06-22 21:39:10.362 [Thread-1] INFO i.s.tm.api.DefaultGlobalTransaction - transaction end, xid = 172.16.185.176:8091:5260755181745868803
2023-06-22 21:39:10.362 [Thread-1] INFO i.s.tm.api.DefaultGlobalTransaction - [172.16.185.176:8091:5260755181745868803] commit status: Committed
2023-06-22 21:39:11.156 [rpcDispatch_RMROLE_1_1_32] INFO i.s.c.r.p.c.RmBranchCommitProcessor - rm client handle branch commit process:BranchCommitRequest{xid='172.16.185.176:8091:5260755181745868803', branchId=5260755181745868804, branchType=AT, resourceId='jdbc:mysql://172.16.185.176:3306/seata', applicationData='{"skipCheckLock":true}'}
2023-06-22 21:39:11.157 [rpcDispatch_RMROLE_1_1_32] INFO io.seata.rm.AbstractRMHandler - Branch committing: 172.16.185.176:8091:5260755181745868803 5260755181745868804 jdbc:mysql://172.16.185.176:3306/seata {"skipCheckLock":true}
2023-06-22 21:39:11.158 [rpcDispatch_RMROLE_1_1_32] INFO io.seata.rm.AbstractRMHandler - Branch commit result: PhaseTwo_Committed
感谢支持
更多内容,请移步《超级个体》。