【深入理解SpringCloud微服务】Seata(AT模式)源码解析——开启全局事务

Seata(AT模式)源码解析------开启全局事务

在《Seata(AT模式)源码解析------@GlobalTransactional注解与@globalLock生效的原理》这一篇文章中,我们分析了Seata(AT模式)的原理,以及GlobalTransactionScanner和GlobalTransactionalInterceptor等核心类的源码。

我们分析到了TransactionalTemplate的execute方法,但是没有对execute方法中的"开启全局事务"、"分支事务处理"、"全局事务提交"、"全局事务回滚"等分支处理的代码做分析,我们后续的文章就分别对这些分支处理逐一做详细的分析。

@GlobalTransactional注解原理复习

  1. 当我们的某个方法使用@GlobalTransactional注解修饰,就会被GlobalTransactionScanner扫描到,生成AOP代理对象。
  2. 当我们调用该方法时,会进入指定的AOP拦截器GlobalTransactionalInterceptor,GlobalTransactionalInterceptor会调用TransactionalTemplate的execute方法进行处理
  3. TransactionalTemplate的execute方法在try-catch代码块中事务的模板代码:开启全局事务、执行业务逻辑、全局事务回滚(如果报错)、全局事务提交。

开启全局事务的源码解析

TransactionalTemplate#beginTransaction(TransactionInfo, GlobalTransaction)

java 复制代码
    private void beginTransaction(TransactionInfo txInfo, GlobalTransaction tx) throws TransactionalExecutor.ExecutionException {
        try {
        	// 调用DefaultGlobalTransaction#begin(int, String)方法开启全局事务
            tx.begin(txInfo.getTimeOut(), txInfo.getName());
        } catch () {}
    }

TransactionalTemplate的beginTransaction会调用DefaultGlobalTransaction的begin方法开启全局事务。

DefaultGlobalTransaction#begin(int, String)

java 复制代码
    @Override
    public void begin(int timeout, String name) throws TransactionException {
    	// 请求TC开启全局事务,返回全局事务ID(xid)
        xid = transactionManager.begin(null, null, name, timeout);
        // xid绑定到ThreadLocal
        RootContext.bind(xid);
    }

DefaultGlobalTransaction的begin方法就是调用transactionManager的begin方法请求TC开启全局事务,TC会返回一个xid,然后DefaultGlobalTransaction把xid存入到ThreadLocal中与当前线程绑定。

DefaultTransactionManager#begin

java 复制代码
    public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
        throws TransactionException {
        GlobalBeginRequest request = new GlobalBeginRequest();
        request.setTransactionName(name);
        request.setTimeout(timeout);
        // 请求TC开启全局事务
        GlobalBeginResponse response = (GlobalBeginResponse) syncCall(request);
        // 返回全局事务ID(xid)
        return response.getXid();
    }

DefaultTransactionManager的begin方法调用syncCall方法请求TC开启全局事务,然后返回response对象中的xid。

syncCall方法会通过Netty发送请求。

java 复制代码
    private AbstractTransactionResponse syncCall(AbstractTransactionRequest request) throws TransactionException {
        return (AbstractTransactionResponse) TmNettyRemotingClient.getInstance().sendSyncRequest(request);
    }

DefaultCoordinator#doGlobalBegin(GlobalBeginRequest, GlobalBeginResponse, RpcContext)

java 复制代码
    @Override
    protected void doGlobalBegin(GlobalBeginRequest request, GlobalBeginResponse response, RpcContext rpcContext)
        throws TransactionException {
        // core.begin(...)开启全局事务,返回xid
        // 然后把xid设置到response对象钟
        response.setXid(core.begin(rpcContext.getApplicationId(), rpcContext.getTransactionServiceGroup(),
            request.getTransactionName(), request.getTimeout()));
    }

TC接收到请求后,会调用DefaultCoordinator的doGlobalBegin方法进行处理。

doGlobalBegin方法会调用DefaultCore的begin方法记录全局事务信息,然后返回xid。

DefaultCore#begin

java 复制代码
    @Override
    public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
        throws TransactionException {
        // 创建GlobalSession
        // 里面会 new GlobalSession()
        // GlobalSession构造方法会生成xid
        GlobalSession session = GlobalSession.createGlobalSession(applicationId, transactionServiceGroup, name,
            timeout);
		
		// 这里会持久化全局事务记录
        session.begin();

		// 返回xid
        return session.getXid();
    }

DefaultCore的begin方法会调用GlobalSession的静态方法createGlobalSession创建一个GlobalSession。

创建了GlobalSession之后会调用GlobalSession的begin方法。该方法会持久化全局事务信息。

GlobalSession.createGlobalSession

java 复制代码
    public static GlobalSession createGlobalSession(String applicationId, String txServiceGroup, String txName,
                                                    int timeout) {
        GlobalSession session = new GlobalSession(applicationId, txServiceGroup, txName, timeout);
        return session;
    }

GlobalSession的createGlobalSession里面会new一个GlobalSession。

java 复制代码
    public GlobalSession(String applicationId, String transactionServiceGroup, String transactionName, int timeout) {
        this.transactionId = UUIDGenerator.generateUUID();
        ...
        this.xid = XID.generateXID(transactionId);
    }

GlobalSession的构造方法会生成一个xid。

生成了GlobalSession之后,DefaultCore就会调用它的begin方法。

GlobalSession#begin()

java 复制代码
    @Override
    public void begin() throws TransactionException {
        for (SessionLifecycleListener lifecycleListener : lifecycleListeners) {
        	// 进入到AbstractSessionManager#onBegin方法
            lifecycleListener.onBegin(this);
        }
    }

AbstractSessionManager#onBegin(GlobalSession)

java 复制代码
    @Override
    public void onBegin(GlobalSession globalSession) throws TransactionException {
        addGlobalSession(globalSession);
    }

    @Override
    public void addGlobalSession(GlobalSession session) throws TransactionException {
        writeSession(LogOperation.GLOBAL_ADD, session);
    }

private void writeSession(LogOperation logOperation, SessionStorable sessionStorable) throws TransactionException {
		// 持久化全局事务信息
        if (!transactionStoreManager.writeSession(logOperation, sessionStorable)) {
            // 省略...
        }
    }

GlobalSession的begin方法一路下来进入到了TransactionStoreManager的writeSession方法。TransactionStoreManager是一个接口,如果我们配置了seata的持久化方式时数据库,那么会进入到DataBaseTransactionStoreManager的writeSession方法。

DataBaseTransactionStoreManager#writeSession

java 复制代码
    @Override
    public boolean writeSession(LogOperation logOperation, SessionStorable session) {
        if (LogOperation.GLOBAL_ADD.equals(logOperation)) {
        	// 插入全局事务信息
            return logStore.insertGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));
        } else if (LogOperation.GLOBAL_UPDATE.equals(logOperation)) {
        	// 更新全局事务信息
            return logStore.updateGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));
        } else if (LogOperation.GLOBAL_REMOVE.equals(logOperation)) {
        	// 删除全局事务信息
            return logStore.deleteGlobalTransactionDO(SessionConverter.convertGlobalTransactionDO(session));
        } else if (LogOperation.BRANCH_ADD.equals(logOperation)) {
        	// 新增分支事务信息
            return logStore.insertBranchTransactionDO(SessionConverter.convertBranchTransactionDO(session));
        } else if (LogOperation.BRANCH_UPDATE.equals(logOperation)) {
        	// 更新分支事务信息
            return logStore.updateBranchTransactionDO(SessionConverter.convertBranchTransactionDO(session));
        } else if (LogOperation.BRANCH_REMOVE.equals(logOperation)) {
        	// 删除分支事务信息
            return logStore.deleteBranchTransactionDO(SessionConverter.convertBranchTransactionDO(session));
        } else {
            throw new StoreException("Unknown LogOperation:" + logOperation.name());
        }
    }
java 复制代码
@Override
    public boolean insertGlobalTransactionDO(GlobalTransactionDO globalTransactionDO) {
        String sql = LogStoreSqlsFactory.getLogStoreSqls(dbType).getInsertGlobalTransactionSQL(globalTable);
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            int index = 1;
            conn = logStoreDataSource.getConnection();
            conn.setAutoCommit(true);
            ps = conn.prepareStatement(sql);
            ps.setString(index++, globalTransactionDO.getXid()); // xid
            ps.setLong(index++, globalTransactionDO.getTransactionId());
            ps.setInt(index++, globalTransactionDO.getStatus()); // 全局事务状态
            ps.setString(index++, globalTransactionDO.getApplicationId());
            ps.setString(index++, globalTransactionDO.getTransactionServiceGroup());
            String transactionName = globalTransactionDO.getTransactionName();
            transactionName = transactionName.length() > transactionNameColumnSize ?
                    transactionName.substring(0, transactionNameColumnSize) :
                    transactionName;
            ps.setString(index++, transactionName);
            ps.setInt(index++, globalTransactionDO.getTimeout());
            ps.setLong(index++, globalTransactionDO.getBeginTime());
            ps.setString(index++, globalTransactionDO.getApplicationData());
            return ps.executeUpdate() > 0;
        } catch (SQLException e) {
            throw new StoreException(e);
        } finally {
            IOUtil.close(ps, conn);
        }
    }

最终会通过jdbc的方式把当前全局事务的信息写入到global_table表。

sql 复制代码
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for global_table
-- ----------------------------
DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table`  (
  `xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '全局事务ID',
  `transaction_id` bigint(20) NULL DEFAULT NULL COMMENT '事务ID',
  `status` tinyint(4) NOT NULL COMMENT '状态',
  `application_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '应用ID',
  `transaction_service_group` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '事务分组名',
  `transaction_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '执行事务的方法',
  `timeout` int(11) NULL DEFAULT NULL COMMENT '超时时间',
  `begin_time` bigint(20) NULL DEFAULT NULL COMMENT '开始时间',
  `application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '应用数据',
  `gmt_create` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `gmt_modified` datetime(0) NULL DEFAULT NULL COMMENT '修改时间',
  PRIMARY KEY (`xid`) USING BTREE,
  INDEX `idx_gmt_modified_status`(`gmt_modified`, `status`) USING BTREE,
  INDEX `idx_transaction_id`(`transaction_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;
相关推荐
零度@8 小时前
Java中Map的多种用法
java·前端·python
中文很快乐8 小时前
java开发--开发工具全面介绍--新手养成记
java·开发语言·java开发·开发工具介绍·idea开发工具
我命由我123458 小时前
python-dotenv - python-dotenv 快速上手
服务器·开发语言·数据库·后端·python·学习·学习方法
yaoxin5211238 小时前
268. Java Stream API 入门指南
java·开发语言·python
ss2738 小时前
ConcurrentLinkedQueue实战:电商秒杀系统的队列选型优化
java·开发语言·安全
繁星蓝雨8 小时前
Qt优雅的组织项目结构三(使用CMakeLists进行模块化配置)——————附带详细示例代码
开发语言·数据库·qt
Wang's Blog9 小时前
RabbitMQ: 高并发外卖系统的微服务架构设计与工程实现
分布式·微服务·rabbitmq
宇擎智脑科技9 小时前
Flutter 对接高德地图 SDK 适配鸿蒙踩坑记录与通信架构解析
flutter·架构·harmonyos
BD_Marathon9 小时前
【JavaWeb】Servlet_jar包导入和Content-Type问题
java·servlet·jar