Seata—分布式事务(2)

45 阅读18分钟

分布式事务基础理论

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 的工作方式

    1. Start Transaction:事务开始。
    2. Prepare:所有参与者准备提交,并报告其准备状态。
    3. Commit/Rollback:根据协调者的指示,所有参与者要么提交事务,要么回滚事务。
  • 优点:XA 是一个标准化协议,广泛被数据库和中间件系统支持,保证了不同系统之间的事务一致性。

  • 缺点:和 2PC 一样,XA 也面临同样的缺点,如协调者故障恢复复杂性等。

  • MYSQL 的 XA Transaction:dev.mysql.com/doc/refman/…

TCC(Try-Confirm-Cancel)

TCC 是一种侵入式的分布式事务解决方案,以下三个操作都需要业务系统自行实现,对业务系统有着非常大的入侵性,设计相对复杂,但是 TCC 完全不依赖数据库,能够实现跨数据库、跨应用资源管理,对这些不同数据访问通过侵入式的编码方式实现一个原子操作,更好地解决了在各种复杂业务场景下的分布式事务问题。

  1. Try:对业务资源的检查并预留;
  2. Confirm:对业务处理进行提交,即 commit 操作,只要 Try 成功,那么该步骤一定成功
  3. Cancel:对业务处理进行取消,即回滚操作,该步骤会对 Try 预留的资源进行释放。

XA与TCC的对比:

  • XA是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。
  • TCC是业务层面的分布式事务,最终一致性,不会一直持有资源的锁。

总结

XA、TCC 和 2PC 都用于管理分布式事务,但各有特点:

  • 2PC 是一种分布式事务的管理协议
  • XA 在 2PC 的基础上标准化的一种实现,用于协调分布式事务的多个参与者,强调事务一致性和标准化。
  • TCC 是将事务分为 Try、Confirm 和 Cancel 三个阶段,提供了更高的灵活性和容错能力。 总体来说,2PC 和 XA 更侧重于一致性和标准化,TCC 则注重灵活性和容错能力。

seata

  1. 2019 年 1 月,阿里巴巴集团正式开源了项目 Fescar (Fast & Easy Commit and Rollback))。
  2. 2019 年 4 月,Fescar 改名为 Seata(Simple Extensible Autonomous Transaction Architecture)。
  3. 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.apache.org/zh-cn/docs/…

与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 获取到不同的事件处理器 image.png
@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

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

一阶段

image.png

  • 如何保证任何提交的业务数据的更新一定有相应的回滚日志存在?

    • 利用本地事务的 ACID 特性,将业务数据的更新和回滚日志的写入一起提交。
  • 如何保证提交时的数据写隔离?

    • 本地事务在提交之前, 需要通过 RM 向 TC 注册本地分支,这个注册过程中会根据刚才执行的 SQL 拿到所有涉及到的数据主键,以 resourceId + tableName + rowPK 作为锁的 key,向 TC 申请所有涉及数据的写锁,当获得所有相关数据的写锁后,再执行本地事务的 Commit 过程。如果有任何一行数据的写锁没有拿到的话,TC 会以 fastfail 的方式回复该 RM,RM 会以重试 + 超时机制重复该过程,直到超时。 image.png
  • 业务sql避免使用seata受限的sql,seata.apache.org/zh-cn/docs/…

二阶段——提交

如果 TM 决议是全局提交,此时分支事务实际上已经完成提交,TC 立刻释放该全局事务的所有锁,然后异步调用 RM 清理回滚日志,Phase2 可以非常快速地完成。 image.png

二阶段——回滚

如果决议是全局回滚,RM 收到协调器发来的回滚请求,通过 XID 和 Branch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚。当分支回滚顺利结束时,通知 TC 回滚完成,这时候 TC 才释放该分支事务相关的所有锁。

image.png 这里有一个需要注意的点,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

image.png

tc server process

image.png

seata client process tc server request

image.png

参考文章: