3-fescar源码分析-分支事务注册

3-fescar源码分析-分支事务注册

一、官方介绍

3.RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。

那这一篇主要分析fescar如何执行业务逻辑?TM 获取到全局事务及XID后,开始执行各个rpc服务的业务逻辑,那么此时如何将各个rpc进行划分成分支事务并且注册到TC,进而如何准备数据集回滚脚本的。

--

二、(原理)源码分析

紧接着上一篇的TC获取全局事务及XID分析,依然借助官网的example例图进行出发。

2.1 demo
  • 继续看下官网的结构图:


    3-fescar源码分析-分支事务注册_第1张图片
    image.png
项目中存在官方的example模块,里面就模拟了上图的相关流程:先回到本节主题:**分支事务注册**
2.2 rpc业务执行
  • TransactionalTemplate执行业务逻辑

    public Object execute(TransactionalExecutor business) throws TransactionalExecutor.ExecutionException {
            ...
            // Do Your Business
            rs = business.execute();
            ...
            return rs;
        }
    

    这里就是具体的rpc服务的逻辑业务了,即:BusinessServiceImpl中的加了注解的@GlobalTransactional方法

    @Override
    @GlobalTransactional(timeoutMills = 300000, name = "dubbo-demo-tx")
    public void purchase(String userId, String commodityCode, int orderCount) {
        LOGGER.info("purchase begin ... xid: " + RootContext.getXID());
        storageService.deduct(commodityCode, orderCount);
        orderService.create(userId, commodityCode, orderCount);
        throw new RuntimeException("xxx");
    }
    

    紧接着调用rpc服务StorageServiceImpl 的deduct方法:

    @Override
    public void deduct(String commodityCode, int count) {
        ...
        jdbcTemplate.update("update storage_tbl set count = count - ? where commodity_code = ?", new Object[] {count, commodityCode});
        ...
    }
    

    回头看到配置文件中:

    
        
    
    
        
    
    
    
        
    
    
  • JdbcTemplate分析

    此处jdbcTemplate用的是spring提供的,且dataSource指向的是com.alibaba.fescar.rm.datasource.DataSourceProxy.
    debug跟踪JdbcTemplate逻辑到一下方法:

    @Override
    public  T execute(PreparedStatementCreator psc, PreparedStatementCallback action)
            throws DataAccessException {
    
        ...
    
        Connection con = DataSourceUtils.getConnection(getDataSource());
        PreparedStatement ps = null;
        try {
            Connection conToUse = con;
            if (this.nativeJdbcExtractor != null &&
                    this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) {
                conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
            }
            ps = psc.createPreparedStatement(conToUse);
            applyStatementSettings(ps);
            PreparedStatement psToUse = ps;
            if (this.nativeJdbcExtractor != null) {
                psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps);
            }
            T result = action.doInPreparedStatement(psToUse);
            handleWarnings(ps);
            return result;
        }
        catch (SQLException ex) {
            ...     }
        finally {
            ...
        }
    }
    

    核心逻辑就在:

    Connection con = DataSourceUtils.getConnection(getDataSource());
    
    #JdbcAccessor
    public DataSource getDataSource() {
        return this.dataSource;
    }
    
    #JdbcAccessor
    @Override
    public ConnectionProxy getConnection() throws SQLException {
        assertManaged();
        Connection targetConnection = targetDataSource.getConnection();
        return new ConnectionProxy(this, targetConnection, targetDataSource.getDbType());
    }
    
    @Override
    public  T execute(PreparedStatementCreator psc, PreparedStatementCallback action)
            throws DataAccessException {
    
        ...
    
        Connection con = DataSourceUtils.getConnection(getDataSource());
        PreparedStatement ps = null;
        try {
            Connection conToUse = con;
            if (this.nativeJdbcExtractor != null &&
                    this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) {
                conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
            }
            ps = psc.createPreparedStatement(conToUse);
            applyStatementSettings(ps);
            PreparedStatement psToUse = ps;
            if (this.nativeJdbcExtractor != null) {
                psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps);
            }
            T result = action.doInPreparedStatement(psToUse);
            handleWarnings(ps);
            return result;
        }
        catch (SQLException ex) {
            ...
        }
        finally {
            ...     }
    }
    

    jdbcTemplate继承至JdbcAccessor,还记的开始的配置文件中配置的dataSource吗?因此该获取的dataSource指向的是com.alibaba.fescar.rm.datasource.DataSourceProxy。
    那么继续看getConnection就是自然调用的是DataSourceProxy中的,最终返回ConnectionProxy。
    紧接着ps = psc.createPreparedStatement(conToUse);调用的就是AbstractConnectionProxy#createPreparedStatement()逻辑,返回PreparedStatementProxy。
    继续往后追踪到executeUpdate逻辑:

    自然此时这里的executeUpdate;就是PreparedStatementProxy中的方法体:

    @Override
    public int executeUpdate() throws SQLException {
        return ExecuteTemplate.execute(this, new StatementCallback() {
            @Override
            public Integer execute(PreparedStatement statement, Object... args) throws SQLException {
                return statement.executeUpdate();
            }
        });
    }
    

    以上就是整个jdbc的追溯逻辑了,进而跟踪后面的代码流程。

  • ExecuteTemplate

    public static  T execute(SQLRecognizer sqlRecognizer,
                                                     StatementProxy statementProxy,
                                                     StatementCallback statementCallback,
                                                     Object... args) throws SQLException {
    
        if (!RootContext.inGlobalTransaction()) {
            // Just work as original statement
            return statementCallback.execute(statementProxy.getTargetStatement(), args);
        }
    
        if (sqlRecognizer == null) {
            sqlRecognizer = SQLVisitorFactory.get(
                    statementProxy.getTargetSQL(),
                    statementProxy.getConnectionProxy().getDbType());
        }
        Executor executor = null;
        if (sqlRecognizer == null) {
            executor = new PlainExecutor(statementProxy, statementCallback);
        } else {
            switch (sqlRecognizer.getSQLType()) {
                case INSERT:
                    executor = new InsertExecutor(statementProxy, statementCallback, sqlRecognizer);
                    break;
                case UPDATE:
                    executor = new UpdateExecutor(statementProxy, statementCallback, sqlRecognizer);
                    break;
                case DELETE:
                    executor = new DeleteExecutor(statementProxy, statementCallback, sqlRecognizer);
                    break;
                case SELECT_FOR_UPDATE:
                    executor = new SelectForUpdateExecutor(statementProxy, statementCallback, sqlRecognizer);
                    break;
                default:
                    executor = new PlainExecutor(statementProxy, statementCallback);
                    break;
            }
        }
        T rs = null;
        try {
            rs = executor.execute(args);
    
        } catch (Throwable ex) {
           ...
        }
        return rs;
    }
    
    • 根据不同的语句生成不同的执行器
    • 具体实现会保存更改前后的数据镜像并插入到undo log里并commit。只有回滚用undo log里的数据生成sql语句回滚
    • 由于AT事务在第一阶段已提交,所以commit过程是由Asyncworker异步删除undo log,真正的commit是在第一阶段完成的
    @Override
    public Object execute(Object... args) throws Throwable {
        String xid = RootContext.getXID();
        statementProxy.getConnectionProxy().bind(xid);
        return doExecute(args);
    }
    

    获取XID,将当前数据库连接绑定到XID,执行逻辑:

    protected T executeAutoCommitTrue(Object[] args) throws Throwable {
        T result = null;
        AbstractConnectionProxy connectionProxy = statementProxy.getConnectionProxy();
        LockRetryController lockRetryController = new LockRetryController();
        try {
            connectionProxy.setAutoCommit(false);
            while (true) {
                try {
                    result = executeAutoCommitFalse(args);
                    connectionProxy.commit();
                    break;
                } catch (LockConflictException lockConflict) {
                    lockRetryController.sleep(lockConflict);
                }
            }
    
        } catch (Exception e) {
           ...
        return result;
    }
    
     protected T executeAutoCommitFalse(Object[] args) throws Throwable {
        TableRecords beforeImage = beforeImage();
        T result = statementCallback.execute(statementProxy.getTargetStatement(), args);
        TableRecords afterImage = afterImage(beforeImage);
        statementProxy.getConnectionProxy().prepareUndoLog(sqlRecognizer.getSQLType(), sqlRecognizer.getTableName(), beforeImage, afterImage);
        return result;
    }
    

    上叙代码很清晰:

    • 1.获取脚本执行前数据镜像
    • 2.执行脚本逻辑(此时还未commit)
    • 3.获取脚本执行后数据镜像
    • 4.根据前后数据镜像及XID等信息准备回滚的ubdo脚本

    执行完以上逻辑后,commit,此处的commit是将上叙执行结果集回滚的脚本进行提交,但提交前要做一件重要的事,那就是本节中重心:注册分支事务

    connectionProxy.commit();
    
    #ConnectionProxy
    @Override
    public void commit() throws SQLException {
        if (context.inGlobalTransaction()) {
            try {
                /** 去TC注册分支事务 */
                register();
                ...
                UndoLogManager.flushUndoLogs(this);
                targetConnection.commit();
            } catch (Throwable ex) {
                ...
            }
            report(true);
            context.reset();
            
        } else {
            targetConnection.commit();
        }
    }
    

    --

2.3.RM 注册分支事务
  • branchRegister 注册事务分支

    private void register() throws TransactionException {
        Long branchId = DataSourceManager.get().branchRegister(BranchType.AT, getDataSourceProxy().getResourceId(), null, context.getXid(), context.buildLockKeys());
        context.setBranchId(branchId);
    }
    
    # DataSourceManager
    @Override
    public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid, String lockKeys) throws TransactionException {
        try {
            BranchRegisterRequest request = new BranchRegisterRequest();
            request.setTransactionId(XID.getTransactionId(xid));
            request.setLockKey(lockKeys);
            request.setResourceId(resourceId);
    
            BranchRegisterResponse response = (BranchRegisterResponse) RmRpcClient.getInstance().sendMsgWithResponse(request);
            if (response.getResultCode() == ResultCode.Failed) {
                throw new TransactionException(response.getTransactionExceptionCode(), "Response[" + response.getMsg() + "]");
            }
            return response.getBranchId();
        } catch (TimeoutException toe) {
           ...
        }
    }
    
    • 1.通过rpcclient到服务端注册分支事务
    • 2.将返回的事务分支BranchId保存进ConnectionContext,用以事务分支的对称。

    上面的代码跟开启事务逻辑基本一致,同样是发送“注册分支事务”消息给server端,server收到事务分支注册消息后进行逻辑处理。

    分支事务注册成功之后,数据逻辑执行、回滚脚本执行,完成整个分支事务的注册。

    那么接下类分析的是server端收到事务分支的注册信息后如何处理了。

    --

2.3.TC 收到消息,开启处理分支事务的注册
  • 前面接受netty消息的逻辑与begin事务逻辑类似,此处不做强调,直接导核心逻辑:处理分支注册

    #DefaultCoordinator
    @Override
    protected void doBranchRegister(BranchRegisterRequest request, BranchRegisterResponse response,
                                    RpcContext rpcContext) throws TransactionException {
        response.setTransactionId(request.getTransactionId());
        response.setBranchId(
            core.branchRegister(request.getBranchType(), request.getResourceId(), rpcContext.getClientId(),
                XID.generateXID(request.getTransactionId()), request.getLockKey()));
    }
    

    继续跟踪:

    @Override
    public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid, String lockKeys) throws TransactionException {
        GlobalSession globalSession = assertGlobalSession(XID.getTransactionId(xid), GlobalStatus.Begin);
    
        BranchSession branchSession = new BranchSession();
        branchSession.setTransactionId(XID.getTransactionId(xid));
        branchSession.setBranchId(UUIDGenerator.generateUUID());
        branchSession.setApplicationId(globalSession.getApplicationId());
        branchSession.setTxServiceGroup(globalSession.getTransactionServiceGroup());
        branchSession.setBranchType(branchType);
        branchSession.setResourceId(resourceId);
        branchSession.setLockKey(lockKeys);
        branchSession.setClientId(clientId);
    
        if (!branchSession.lock()) {
            throw new TransactionException(LockKeyConflict);
        }
        try {
            globalSession.addBranch(branchSession);
        } catch (RuntimeException ex) {
            throw new TransactionException(FailedToAddBranch);
    
        }
        return branchSession.getBranchId();
    }
    

    逻辑很明晰:

    • 1.根据XID获取全局事务GlobalSession
    • 2.构造一个事务分支:BranchSession,并赋值相关属性
    • 3.将BranchSession进行上锁处理
    • 4.将分支事务BranchSession加入全局事务GlobalSession
    • 5.返回创建的branchId给客户端
    • 6.完成分支的注册工作。

    此时 TC 与 RM 即维持了同步的branchId信息。

    至此,事务分支的注册完成。

--

三.未完待续。。。

后续分析主要还是根据example官方实例分为:事务回滚、事务提交进行。
同时后续每一流程都紧密关联Server,因此还会频繁回到上叙server启动后,收到消息被触发的后续逻辑。

你可能感兴趣的:(3-fescar源码分析-分支事务注册)