分布式事务基础理论
CAP定理和Base理论是解决分布式事务的具体方向
CAP定理
1998年、加州大学的计算机科学家Eric Brewer提出,分布式系统有三个指标
- Consistency(一致性)
- Avaliability(可用性)
- Partition tolerance(分区容错性)
但是这三个指标是无法同时满足的,这个结论就叫做CAP定理。分布式系统一定会出现分区(P)问题,当分区必须出现时,一致性(C)和可用性(A)就无法同时满足。
BASE 理论
BASE 理论实际是对CAP的一种解决思路,其中包含三个思想:
- Basically Available(基本可用):分布式系统在出现故障时,允许损失部分性能,来保证核心可用
- Soft State(软状态):在一定时间内,允许出现中间状态,比如临时的不一致状态
- Eventually Consistent(最终一致性):虽无法保证强一致性,但是在软状态结束后,最终到达数据一致
分布式事务解决依据
分布式事务的最大的问题就是各个子事务的一致性,因此可以借鉴CAP定理和Base理论
- AP:各个子事务分别执行和提交,允许一段时间内结果的不一致,但是必须采用弥补措施,实现数据的最终一致。
- CP:各个子事务执行后相互等待,同时提交,同时回滚,达成强一致。但事务等待过程中,处于弱可用状态。
分布式事务模型:要解决分布式事务,各个子系统之间必须感知到彼此的事务状态,才能保证事务状态的一致,因此需要一个事务协调者来协调每一个事务的参与者(子系统事务)。
mysql有关事务常用命令
SELECT @@global.transaction_isolation;
SELECT @@session.transaction_isolation;
start transaction;
rollback/commit;
# `INFORMATION_SCHEMA` 数据库提供了关于正在运行的事务的信息:
SELECT * FROM information_schema.innodb_trx \G;
基本概念
2PC(Two Phase Commit)
2PC 一种分布式事务的管理协议,用于确保在分布式系统中所有参与者在事务提交时保持一致。
- 准备阶段(Prepare Phase):资源的预留和锁定
- 提交阶段(Commit/Rollback Phase):提交或者回滚
缺点:单点故障、资源占用,服务阻塞,如果协调者在第二阶段(提交阶段)之前宕机了,参与者无法确定事务的最终结果(提交还是回滚),并且可能会等待协调者的恢复或超时。这种阻塞可能导致资源的长期占用和系统的不可用。
XA(eXtended Architecture)
XA 是一种标准协议,基于 2PC 的机制进行事务管理,但在其标准中定义了具体的操作和接口。用于在分布式事务中实现两阶段提交。它是由 X/Open(现在是 Open Group)定义的分布式事务处理(DTP, Distributed Transaction Processing)标准。XA 规范描述了全局的TM与局部的RM之间的接口,几乎所有主流数据库都对XA规范提供了支持。
-
XA 的工作方式:
- Start Transaction:事务开始。
- Prepare:所有参与者准备提交,并报告其准备状态。
- Commit/Rollback:根据协调者的指示,所有参与者要么提交事务,要么回滚事务。
-
优点:XA 是一个标准化协议,广泛被数据库和中间件系统支持,保证了不同系统之间的事务一致性。
-
缺点:和 2PC 一样,XA 也面临同样的缺点,如协调者故障恢复复杂性等。
-
MYSQL 的 XA Transaction:dev.mysql.com/doc/refman/…
TCC(Try-Confirm-Cancel)
TCC 是一种侵入式的分布式事务解决方案,以下三个操作都需要业务系统自行实现,对业务系统有着非常大的入侵性,设计相对复杂,但是 TCC 完全不依赖数据库,能够实现跨数据库、跨应用资源管理,对这些不同数据访问通过侵入式的编码方式实现一个原子操作,更好地解决了在各种复杂业务场景下的分布式事务问题。
- Try:对业务资源的检查并预留;
- Confirm:对业务处理进行提交,即 commit 操作,只要 Try 成功,那么该步骤一定成功;
- Cancel:对业务处理进行取消,即回滚操作,该步骤会对 Try 预留的资源进行释放。
XA与TCC的对比:
- XA是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。
- TCC是业务层面的分布式事务,最终一致性,不会一直持有资源的锁。
总结
XA、TCC 和 2PC 都用于管理分布式事务,但各有特点:
- 2PC 是一种分布式事务的管理协议
- XA 在 2PC 的基础上标准化的一种实现,用于协调分布式事务的多个参与者,强调事务一致性和标准化。
- TCC 是将事务分为 Try、Confirm 和 Cancel 三个阶段,提供了更高的灵活性和容错能力。 总体来说,2PC 和 XA 更侧重于一致性和标准化,TCC 则注重灵活性和容错能力。
seata
- 2019 年 1 月,阿里巴巴集团正式开源了项目 Fescar (Fast & Easy Commit and Rollback))。
- 2019 年 4 月,Fescar 改名为 Seata(Simple Extensible Autonomous Transaction Architecture)。
- 2023 年 10 月,Seata 捐赠给 Apache 基金会,Seata 正式进入 Apache 孵化器。
在 Seata 的架构中,一共有三个角色:seata.apache.org/zh-cn/docs/…
- TC (Transaction Coordinator) - Seata Server
- TM (Transaction Manager) - 事务发起者 (首次调用带有@GlobalTransaction注解的方法)
- RM (Resource Manager) - 事务参与者 (微服务中对应的数据库)
AT 模式
Seata AT模式的核心是对业务无侵入,是一种改进后的两阶段提交
- 一阶段:注册分支事务、记录 undolog 日志、执行业务sql并提交,释放本地锁和连接资源。
- 二阶段:提交异步化,非常快速地完成。
- 事务操作成功,TC通知RM异步删除undolog
- 事务操作失败,TM向TC发送回滚请求,RM 收到协调器TC发来的回滚请求,通过 XID 和 Branch ID 找到相应的undolog日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚。
注意:在调用其他服务时,如果发生异常必须将异常抛出,否则TM无法感知是否异常
XA模式
与Seata 支持的其它事务模式不同,XA 协议要求事务资源本身提供对规范和协议的支持,所以事务资源(如数据库)可以保障从任意视角对数据的访问有效隔离,满足全局数据一致性。
- 业务无侵入
- 数据库的支持广泛
缺点:XA prepare 后,分支事务进入阻塞阶段,收到 XA commit 或 XA rollback 前必须阻塞等待。事务资源长时间得不到释放,锁定周期长,而且在应用层上面无法干预,性能差。
Spring Cloud Alibaba整合Seata XA的对比AT模式配置:
- 微服务数据库不需要undo_log表,undo_log表仅用于AT模式
- 修改数据源代码模式为XA模式
seata:
data-source-proxy-mode: XA
- seata1.4.0服务端支持XA,但是客户端却不支持,客户端需要排除seata1.3.0的依赖包
implementation('com.alibaba.cloud:spring-cloud-starter-alibaba-seata') {
exclude(group: 'io.seata', module:'seata-spring-boot-starter')
}
implementation 'io.seata:seata-spring-boot-starter:1.4.0'
TCC模式
SEATA的TCC是手工的AT模式,它允许你自定义两阶段的处理逻辑而不依赖AT模式的undo_log。
AT:
- prepare:在本地事务中,一并提交业务数据更新和相应回滚日志记录。
- commit:马上成功结束,自动异步批量清理回滚日志。
- rollback:通过回滚日志,自动生成补偿操作,完成数据回滚。
TCC:prepare-commit-rollback 都是调用自定义的逻辑
空回滚、业务悬挂
- 幂等:TC 重复进行二阶段commit/rollback,导致资源重复提交或者重复释放。
- 空回滚:未执行try,而执行了cancel
- 当某个分支事务的try阶段阻塞时,可能导致全局事务超时而触发二阶段的cancel操作。
- 业务悬挂:执行了try,而无法继续执行cancel/commit
- 对于已经空回滚的业务,如果以后继续执行try,导致不能执行confirm或者cancel操作; 应该阻止执行空回滚后的try操作,避免悬挂。
TCC 模式中存在的三大问题幂等、悬挂和空回滚。在 Seata1.5.1中,增加了一张事务控制表 tcc_fence_log 来解决这些问题。@TwoPhaseBusinessAction 注解新增useTCCFence(默认false)属性指定是否开启这个机制。
Seata 的核心类
全局事务ID:TM 获取到全局事务ID后需要将全局事务ID传递到下游服务
- SeataFeignClientAutoConfiguration:seata整合feign的配置
- SeataHandlerInterceptor:从请求中获取全局事务ID
- SeataFeignClient:传递全局事务ID
Spring整合Seata
- SeataAutoConfiguration
- GlobalTransactionScanner
- SeataAutoDataSourceProxyCreator
- GlobalTransactionalInterceptor
- TransactionalTemplate
- TransactionManager
- DefaultTransactionManager
- GlobalTransaction
- DefaultGlobalTransaction
- ResourceManager
- DefaultRMHandler
- RMClient
GlobalTransactionScanner
- GlobalTransactionScanner
extends AbstractAutoProxyCreator implements SmartInstantiationAwareBeanPostProcessor
,会在spring容器启动初始化bean的时候,对bean进行代理操作。wrapIfNecessary
目标方法织入GlobalTransactionalInterceptor
- initClient() 初始化了以下两个远程调用客户端,用于发起请求和监听结果
- TmNettyRemotingClient:事务管理器客户端
- RmNettyRemotingClient:资源管理器客户端
private void initClient() { //init TM TMClient.init(applicationId, txServiceGroup); //init RM RMClient.init(applicationId, txServiceGroup); }
RMClient
RM客户端实例化工具类,用于创建 RM 客户端, GlobalTransactionScanner.initClient() 方法,在bean对象创建时进行初始化,与之相同的还有一个 TMClient 逻辑都一样
public class RMClient {
public static void init(String applicationId, String transactionServiceGroup) {
//通过单例模式创建一个Netty的 RM客户端端
RmNettyRemotingClient rmNettyRemotingClient = RmNettyRemotingClient.getInstance(applicationId, transactionServiceGroup);
//设置默认的资源管理器
rmNettyRemotingClient.setResourceManager(DefaultResourceManager.get());
/**
* 设置事务方法的处理器,默认 DefaultRMHandler,其中根据类型选择对于模式的处理器
* RMHandlerAT
* RMHandlerSaga
* RMHandlerTCC
* RMHandlerXA
*/
rmNettyRemotingClient.setTransactionMessageHandler(DefaultRMHandler.get());
//初始化时注册对于方法类型的处理器
rmNettyRemotingClient.init();
}
}
RemotingClient
远程调用客户端,用于 Client 与 Server 的通信,目前客户端的实现就以下两个:
-
TM用于一阶段的通信
-
RM用于二阶段接收TC对事务的处理
-
RmNettyRemotingClient:资源管理器客户端,管理分支事务处理的资源,驱动分支事务提交或回滚,与TC进行通信以注册分支事务和报告分支事务的状态
-
TmNettyRemotingClient:事务管理器,定义全局事务的范围,开始全局事务、提交或回滚全局事务
-
RmNettyRemotingClient#registerProcessor RM 注册消息类型的处理器
-
TmNettyRemotingClient#registerProcessor TM 注册消息类型的处理器
TransactionManager
- TransactionManager:全局事务管理器,持有 TmNettyRemotingClient TMClient 对象,调用 TC 开启、提交、回滚全局事务等,
- DefaultTransactionManager:TransactionManager 默认实现类
- TransactionManagerHolder:TransactionManager(默认DefaultTransactionManager)的持有者, 可通过 EnhancedServiceLoader的spi机制自定义TransactionManager。
ResourceManager
- ResourceManager:资源管理器,分支事务的注册、提交、回滚等操作
- DefaultResourceManager:根据 BranchType 适配 ResourceManager,具体实现委派给对应的 ResourceManager
- TCCResourceManager:Tcc资源管理器
- DataSourceManager:AT资源管理器
Resource
Resource that can be managed by Resource Manager and involved into global transaction. 被 ResourceManager 管理并且参与到全局事务中,每个Resource都属于一种branchType,branch叫做分支事务,属于全局事务当中的一个节点
DefaultRMHandler
- DefaultRMHandler 资源管理器事件处理器,处理第二阶段的事件;可根据 BranchType 获取到不同的事件处理器
@Override
public BranchCommitResponse handle(BranchCommitRequest request) {
return getRMHandler(request.getBranchType()).handle(request);
}
@Override
public BranchRollbackResponse handle(BranchRollbackRequest request) {
return getRMHandler(request.getBranchType()).handle(request);
}
- DefaultRMHandler 实现了 RMInboundHandler 用来接收 TC 发送的事务提交或者回滚请求
public interface RMInboundHandler {
;
/**
* Handle branch commit response.
*
* @param request the request
* @return the branch commit response
*/
BranchCommitResponse handle(BranchCommitRequest request);
/**
* Handle branch rollback response.
*
* @param request the request
* @return the branch rollback response
*/
BranchRollbackResponse handle(BranchRollbackRequest request);
/**
* Handle delete undo log .
*
* @param request the request
*/
void handle(UndoLogDeleteRequest request);
}
GlobalTransaction
- GlobalTransaction:全局事务接口,定义了开启,提交,回滚,获取状态等方法。
- DefaultGlobalTransaction:GlobalTransaction 的默认实现
- 持有 TransactionManager 对象,默认开启事务超时时间为60秒,默认名称为default。
- GlobalTransactionRole(多重嵌套) 角色为 Launcher 才可以开启、提交、回滚事务
- GlobalTransactionContext:新建 GlobalTransaction,获取当前线程 GlobalTransaction 等方法。
public interface GlobalTransaction {
/**
* 开启全局事务
*/
void begin() throws TransactionException;
void begin(int timeout) throws TransactionException;
void begin(int timeout, String name) throws TransactionException;
/**
* 提交事务
*/
void commit() throws TransactionException;
/**
* 回滚全局事务
*/
void rollback() throws TransactionException;
/**
* 暂停全局事务
*/
SuspendedResourcesHolder suspend() throws TransactionException;
/**
* 回复一个全局事务
*/
void resume(SuspendedResourcesHolder suspendedResourcesHolder) throws TransactionException;
/**
* 查询当前事务的状态
*/
GlobalStatus getStatus() throws TransactionException;
/**
* 获取XID
*/
String getXid();
/**
* 上报全局事务的状态信息
*/
void globalReport(GlobalStatus globalStatus) throws TransactionException;
/**
* 获取本地事务状态
*/
GlobalStatus getLocalStatus();
/**
* 获取当前全局事务的角色,是事务参与者还是事务发起者
*/
GlobalTransactionRole getGlobalTransactionRole();
}
GlobalTransactionalInterceptor
- 拦截 @GlobalTransactional 或 GlobalLock 注解的方法
- GlobalTransactionalInterceptor#invoke
- GlobalTransactionalInterceptor#handleGlobalTransaction
- TransactionalTemplate#execute
- GlobalTransactionalInterceptor#handleGlobalTransaction
TransactionalTemplate
事务操作模板类
TransactionalTemplate#execute 提供了一个开启事务,执行业务,成功提交和失败回滚的模板方法
public Object execute(TransactionalExecutor business) throws Throwable {
try {
//开启一个事务,发送事务的请求到 TC(事务协调者,也就是Server端)
beginTransaction(txInfo, tx);
Object rs;
try {
// 执行方法,在方法执行时,通过获取到数据源的代理类 DataSourceProxy,执行后续的sql解析创建前后镜像的逻辑
rs = business.execute();
} catch (Throwable ex) {
//抛出异常之后需要进行操作
completeTransactionAfterThrowing(txInfo, tx, ex);
throw ex;
}
//没有异常就提交事务
commitTransaction(tx);
return rs;
} finally {
//最后清除全局锁的配置信息
resumeGlobalLockConfig(previousConfig);
//触发完成之后的钩子函数
triggerAfterCompletion();
//清除钩子函数
cleanUp();
}
}
全局事务——开启:TransactionalTemplate#beginTransaction
private void beginTransaction(TransactionInfo txInfo, GlobalTransaction tx) throws TransactionalExecutor.ExecutionException {
try {
//触发开启事务之前的钩子函数
triggerBeforeBegin();
//开启事务,通过 TM 申请一个全局XID
tx.begin(txInfo.getTimeOut(), txInfo.getName());
//事务开启之后的钩子函数
triggerAfterBegin();
} catch (TransactionException txe) {
//抛出一个 开始事务失败的异常信息,方便外层获取到了之后进行对应的操作
throw new TransactionalExecutor.ExecutionException(tx, txe, TransactionalExecutor.Code.BeginFailure);
}
}
DefaultGlobalTransaction#begin
public void begin(int timeout, String name) throws TransactionException {
// 角色不为事务发起者的话, XID必然不为空 并且 无需开启全局事务
if (role != GlobalTransactionRole.Launcher) {
assertXIDNotNull();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Ignore Begin(): just involved in global transaction [{}]", xid);
}
return;
}
//角色为事务的发起者,XID必然为空
assertXIDNull();
String currentXid = RootContext.getXID();
if (currentXid != null) {
throw new IllegalStateException("Global transaction already exists," +
" can't begin a new global transaction, currentXid = " + currentXid);
}
//通过TM 发起rpc请求获取到全局的XID
xid = transactionManager.begin(null, null, name, timeout);
//事务状态为开启
status = GlobalStatus.Begin;
// 绑定XID
RootContext.bind(xid);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Begin new global transaction [{}]", xid);
}
}
DefaultTransactionManager#begin
- 通过 TmNettyRemotingClient 向TC 发起 GlobalBeginRequest 请求
@Override
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
throws TransactionException {
GlobalBeginRequest request = new GlobalBeginRequest();
request.setTransactionName(name);
request.setTimeout(timeout);
GlobalBeginResponse response = (GlobalBeginResponse) syncCall(request);
if (response.getResultCode() == ResultCode.Failed) {
throw new TmTransactionException(TransactionExceptionCode.BeginFailed, response.getMsg());
}
return response.getXid();
}
private AbstractTransactionResponse syncCall(AbstractTransactionRequest request) throws TransactionException {
try {
return (AbstractTransactionResponse) TmNettyRemotingClient.getInstance().sendSyncRequest(request);
} catch (TimeoutException toe) {
throw new TmTransactionException(TransactionExceptionCode.IO, "RPC timeout", toe);
}
}
AT Mode
一阶段
-
如何保证任何提交的业务数据的更新一定有相应的回滚日志存在?
- 利用本地事务的 ACID 特性,将业务数据的更新和回滚日志的写入一起提交。
-
如何保证提交时的数据写隔离?
- 本地事务在提交之前, 需要通过 RM 向 TC 注册本地分支,这个注册过程中会根据刚才执行的 SQL 拿到所有涉及到的数据主键,以
resourceId + tableName + rowPK
作为锁的 key,向 TC 申请所有涉及数据的写锁,当获得所有相关数据的写锁后,再执行本地事务的 Commit 过程。如果有任何一行数据的写锁没有拿到的话,TC 会以fastfail
的方式回复该 RM,RM 会以重试 + 超时机制重复该过程,直到超时。
- 本地事务在提交之前, 需要通过 RM 向 TC 注册本地分支,这个注册过程中会根据刚才执行的 SQL 拿到所有涉及到的数据主键,以
-
业务sql避免使用seata受限的sql,seata.apache.org/zh-cn/docs/…
二阶段——提交
如果 TM 决议是全局提交,此时分支事务实际上已经完成提交,TC 立刻释放该全局事务的所有锁,然后异步调用 RM 清理回滚日志,Phase2 可以非常快速地完成。
二阶段——回滚
如果决议是全局回滚,RM 收到协调器发来的回滚请求,通过 XID 和 Branch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚。当分支回滚顺利结束时,通知 TC 回滚完成,这时候 TC 才释放该分支事务相关的所有锁。
这里有一个需要注意的点,RM 在进行回滚时,会先跟
afterImage
进行比较:
- 如果一致:则执行逆向 SQL
- 没必要执行回滚 SQL 了,数据已经恢复了
- 如果不一致: 再跟
beforeImage
进行比较- 出现了脏数据,这时候就抛出异常,需要人工处理
核心类
- DataSourceProxy
- ConnectionProxy
- PreparedStatementProxy
- ExecuteTemplate
处理流程:DataSourceProxy获取连接ConnectionProxy,ConnectionProxy获取预编译PreparedStatementProxy,PreparedStatementProxy获取SQL执行器ExecuteTemplate。
DataSourceProxy
- SeataAutoDataSourceProxyCreator 代理DataSource 数据源,代理对象为 SeataDataSourceProxy 类型,根据代理模式创建不同的实现类:
- DataSourceProxy:AT模式
- DataSourceProxyXA:XA模式
- 实现了 Resource,BranchType为AT,可作为 ResourceManager 资源管理对象,会将 registerResource 这件事委托给 ResourceManager;
- DataSourceProxy 作为数据源代理类,
extends AbstractDataSourceProxy
,间接的实现了DataSource
接口 - DataSourceProxy构造过程中会调用init初始化方法,进行Resource的注册
private void init(DataSource dataSource, String resourceGroupId) {
this.resourceGroupId = resourceGroupId;
try (Connection connection = dataSource.getConnection()) {
// 获取url dataType username
jdbcUrl = connection.getMetaData().getURL();
dbType = JdbcUtils.getDbType(jdbcUrl);
if (JdbcConstants.ORACLE.equals(dbType)) {
userName = connection.getMetaData().getUserName();
}
} catch (SQLException e) {
throw new IllegalStateException("can not init dataSource", e);
}
// 获取资源管理器 DataSourceManager,并将资源发送给TC server
DefaultResourceManager.get().registerResource(this);
}
- DataSourceProxy#getConnection获取到的就是代理类,ConnectionProxy#commit判断有没有全局事务,有全局事务注册分支事务。
public ConnectionProxy getConnection() throws SQLException {
Connection targetConnection = targetDataSource.getConnection();
return new ConnectionProxy(this, targetConnection);
}
public abstract class AbstractConnectionProxy implements Connection {
/**
* 数据源代理对象
*/
protected DataSourceProxy dataSourceProxy;
/**
* 原始Connection对象
*/
protected Connection targetConnection;
}
全局事务提交:提交事务时,会先向 TC 注册一个全局锁,表名+行记录 构成的锁;Seata 中对于写隔离的实现就是采用全局锁实现,写的过程:
- 一阶段本地事务提交前,申请全局锁,拿不到全局锁不能提交
- 超出了限制将放弃拿锁,回滚本地事务,释放本地锁
ConnectionProxy#processGlobalTransactionCommit:在事务提交时注册分支事务
public void commit() throws SQLException {
try {
//采用重试机制,反复的获取锁
LOCK_RETRY_POLICY.execute(() -> {
// 执行提交任务
doCommit();
return null;
});
} catch (SQLException e) {
//判断是否是自动提交 并且并没有被改变过
if (targetConnection != null && !getAutoCommit() && !getContext().isAutoCommitChanged()) {
rollback();
}
throw e;
} catch (Exception e) {
throw new SQLException(e);
}
}
private void processGlobalTransactionCommit() throws SQLException {
try {
//向 TC 设置一个由 表名:id 组成的全局锁,返回一个 branchId
register();
} catch (TransactionException e) {
//如果锁已经存在,那么构建一个锁冲突的异常 LockConflictException
recognizeLockKeyConflictException(e, context.buildLockKeys());
}
try {
//刷新undolog日志,提交事务
UndoLogManagerFactory.getUndoLogManager(this.getDbType()).flushUndoLogs(this);
targetConnection.commit();
} catch (Throwable ex) {
LOGGER.error("process connectionProxy commit error: {}", ex.getMessage(), ex);
//上报异常事务状态
report(false);
throw new SQLException(ex);
}
if (IS_REPORT_SUCCESS_ENABLE) {
report(true);
}
context.reset();
}
StatementProxy
StatementProxy:ConnectionProxy 创建 StatementProxy
public abstract class AbstractStatementProxy<T extends Statement> implements Statement {
/**
* ConnectionProxy对象
*/
protected AbstractConnectionProxy connectionProxy;
/**
* 原始Statement对象
*/
protected T targetStatement;
/**
* 原始的待执行sql
*/
protected String targetSQL;
public AbstractStatementProxy(AbstractConnectionProxy connectionProxy, T targetStatement, String targetSQL) throws SQLException {
this.connectionProxy = connectionProxy;
this.targetStatement = targetStatement;
this.targetSQL = targetSQL;
}
}
核心方法 Statement#execute 将被 StatementProxy#execute 代理
@Override
public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
this.targetSQL = sql;
return ExecuteTemplate.execute(this, (statement, args) -> statement.execute((String) args[0],(int)args[1]), sql,autoGeneratedKeys);
}
ExecuteTemplate
ExecuteTemplate 提供了具体执行statement的execute
的模板方法,最终会调用AbstractDMLBaseExecutor#doExecute
方法。
public T doExecute(Object... args) throws Throwable {
AbstractConnectionProxy connectionProxy = this.statementProxy.getConnectionProxy();
return connectionProxy.getAutoCommit() ? this.executeAutoCommitTrue(args) : this.executeAutoCommitFalse(args);
}
protected T executeAutoCommitFalse(Object[] args) throws Exception {
if (!"mysql".equalsIgnoreCase(this.getDbType()) && this.isMultiPk()) {
throw new NotSupportYetException("multi pk only support mysql!");
} else {
TableRecords beforeImage = this.beforeImage();
T result = this.statementCallback.execute(this.statementProxy.getTargetStatement(), args);
TableRecords afterImage = this.afterImage(beforeImage);
this.prepareUndoLog(beforeImage, afterImage);
return result;
}
}
ExecuteTemplate#prepareUndoLog将前置镜像和后置镜像生成一份SQLUndoLog,最终存放到undo_log表中,主要用于事务rollback回滚数据
protected void prepareUndoLog(TableRecords beforeImage, TableRecords afterImage) throws SQLException {
if (beforeImage.getRows().isEmpty() && afterImage.getRows().isEmpty()) {
return;
}
ConnectionProxy connectionProxy = statementProxy.getConnectionProxy();
TableRecords lockKeyRecords = sqlRecognizer.getSQLType() == SQLType.DELETE ? beforeImage : afterImage;
String lockKeys = buildLockKey(lockKeyRecords);
connectionProxy.appendLockKey(lockKeys);
SQLUndoLog sqlUndoLog = buildUndoItem(beforeImage, afterImage);
connectionProxy.appendUndoLog(sqlUndoLog);
}
UndoLogManager
undolog 日志管理器
AsyncWorker
为了提高效率,将事务的提交委派给 AsyncWorker 进行提交的;在异步任务中,删除生成的操作日志。
SQLRecognizer
SQLRecognizer识别sql类型,获取表名,表别名以及原生sql
UndoExecutorFactory
UndoExecutorFactory根据sqlType生成对应的AbstractUndoExecutor。UndoExecutor为生成执行undoSql的核心。如果全局事务回滚,它会根据beforeImage和afterImage以及sql类型生成对应的反向sql执行回滚数据,并添加脏数据校验机制,使回滚数据更加可靠。
TC Server Process
- DefaultCoordinator
DefaultCoordinator
- DefaultCoordinator:全局事务默认的事务协调器(TC)。
- 继承AbstractTCInboundHandler,为TC接收RM和TM的request请求数据,是进行相应处理的处理器。
- 实现TransactionMessageHandler,处理收到的RPC信息。实现ResourceManagerInbound接口,发送至RM的branchCommit,branchRollback请求。
Core
Core:事务协调器TC的核心处理器
- 实现 ResourceManagerOutbound,处理来自RM的rpc请求 branchRegister,branchReport,lockQuery。
- 实现 TransactionManager,处理来自TM的rpc网络请求 begin,commit,rollback,getStatus
GlobalSession
- GlobalSession 实现了SessionLifecycle生命周期接口,提供 begin,changeStatus,changeBranchStatus,addBranch,removeBranch等操作
- TransactionManager#begin 发送 GlobalBeginRequest至 TC server,TC创建GlobalSession,并返回xid
BranchSession
BranchSession为分支session,管理分支数据,受globalSession统一调度管理,它的lock和unlock方法由lockManger实现。
LockManager
- LockManager:DefaultLockManager是LockManager的默认实现,它获取branchSession的lockKey,转换
List<RowLock>
·,委派Locker进行处理。 - Locker:Locker接口提供根据行数据获取锁,释放锁,是否锁住和清除所有锁的方法。
Tcc mode
TccActionInterceptor
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
if (!RootContext.inGlobalTransaction() || disable || RootContext.inSagaBranch()) {
//not in transaction
return invocation.proceed();
}
Method method = getActionInterfaceMethod(invocation);
TwoPhaseBusinessAction businessAction = method.getAnnotation(TwoPhaseBusinessAction.class);
//try method
if (businessAction != null) {
//save the xid
String xid = RootContext.getXID();
//save the previous branchType
BranchType previousBranchType = RootContext.getBranchType();
//if not TCC, bind TCC branchType
if (BranchType.TCC != previousBranchType) {
RootContext.bindBranchType(BranchType.TCC);
}
try {
Object[] methodArgs = invocation.getArguments();
//Handler the TCC Aspect
Map<String, Object> ret = actionInterceptorHandler.proceed(method, methodArgs, xid, businessAction,
invocation::proceed);
//return the final result
return ret.get(Constants.TCC_METHOD_RESULT);
}
finally {
//if not TCC, unbind branchType
if (BranchType.TCC != previousBranchType) {
RootContext.unbindBranchType();
}
}
}
return invocation.proceed();
}
ActionInterceptorHandler
- ActionInterceptorHandler#proceed
- ActionInterceptorHandler#doTccActionLogStore
- 解析 TwoPhaseBusinessAction 注解等信息,包括了回滚或者提交所要执行的方法,并将解析的信息转为 Map 以便于二阶段可以使用
- 直接注册分支事务
TCCResourceManager
RMHandlerTCC
全局事务处理过程
seata client process
tc server process
seata client process tc server request
参考文章: