seata-rm模块浅析

简介

    这篇文章继续谈谈RM模块,之前说过TM,就放在一起谈谈吧。从哪说起呢,还是结合springboot配置那说吧。

浅析

    TM里谈过SeataAutoConfiguration,但是未提及SeataAutoDataSourceProxyCreator,这里就从它说起吧。

public SeataAutoDataSourceProxyCreator(boolean useJdkProxy, String[] excludes, String dataSourceProxyMode) {
  this.excludes = Arrays.asList(excludes);
  this.advisor = new DefaultIntroductionAdvisor(new SeataAutoDataSourceProxyAdvice(dataSourceProxyMode));
  setProxyTargetClass(!useJdkProxy);
}
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource customTargetSource) throws BeansException {
  return new Object[]{advisor};
}

 protected boolean shouldSkip(Class<?> beanClass, String beanName) {
   return !DataSource.class.isAssignableFrom(beanClass) ||
   SeataProxy.class.isAssignableFrom(beanClass) ||
   excludes.contains(beanClass.getName());
 }

    可以看到SeataAutoDataSourceProxyCreator继承了AbstractAutoProxyCreator并重写了上面2个方法,AbstractAutoProxyCreator是AOP的组件,简单提一下,其通过InstantiationAwareBeanPostProcessor对bean提供了包装,即代理bean,其包装逻辑就是:1看看是不是需要包装,2获取bean的所有拦截器。那SeataAutoDataSourceProxyCreator重写了shoudSkip这个比较容易忽略其逻辑,其实就是看beanClass是否是DataSource,是就进行代理,其他两个条件是对是DataSource后的进一步判断不多说,至于GetAdvicesAndAdvisorsForBean就是将SeataAutoDataSourceProxyAdvice用来增强bean。这样一来就是说要对DataSource进行代理,其增强器就是SeataAutoDataSourceProxyAdvice,那就看看它。

public Object invoke(MethodInvocation invocation) throws Throwable {
        if (!RootContext.requireGlobalLock()
                && dataSourceProxyMode != RootContext.getBranchType()) {
            return invocation.proceed();
        }

        Method method = invocation.getMethod();
        Object[] args = invocation.getArguments();
        Method m = BeanUtils.findDeclaredMethod(dataSourceProxyClazz, method.getName(), method.getParameterTypes());
        if (m != null) {
            SeataDataSourceProxy dataSourceProxy = DataSourceProxyHolder.get().putDataSource((DataSource) invocation.getThis(), dataSourceProxyMode);
            return m.invoke(dataSourceProxy, args);
        } else {
            return invocation.proceed();
        }
    }

     这里就比较清楚了,就是对DataSource进行SeataDataSourceProxy代理。根据不同的代理模式获取XA或AT的SeataDataSourceProxy,这里要注意下DataSourceProxyHolder.get()这段代码,在SeataDataSourceBeanPostProcessor也会进行这个处理,DataSourceProxyHolder对SeataDataSourceProxy的进行了缓存。

    说到这里得小结一下,前面提了一大堆就是为了搞一个SeataDataSourceProxy,那么通过它又会得到相应jdbc代理,因为到了持久层无论怎样都要进行jdbc操作(只针对jdbc规范来说),而我们在官网里也能看到seata的AT就是通过undolog来进行事务的二阶段提交的,那要生成对应的unduolog就一定要对sql进行处理,对jdbc规范来说到了持久层无论什么持久层框架最终都要落到jdbc规范上来,那对jdbc进行代理就可以完成uodolog的处理了。

    既然说到了jdbc,那sql的执行就一定得从Statement开始,那就从StatementProxy说起。

public int executeUpdate(String sql) throws SQLException {
        this.targetSQL = sql;
        return ExecuteTemplate.execute(this, (statement, args) -> statement.executeUpdate((String) args[0]), sql);
    }

     StatementProxy实现了Statement,其在执行具体命令时交由ExecuteTemplate去执行,继续看ExecuteTemplate。

public static <T, S extends Statement> T execute(List<SQLRecognizer> sqlRecognizers,
                                                     StatementProxy<S> statementProxy,
                                                     StatementCallback<T, S> statementCallback,
                                                     Object... args) throws SQLException {
        if (!RootContext.requireGlobalLock() && BranchType.AT != RootContext.getBranchType()) {
            // Just work as original statement
            return statementCallback.execute(statementProxy.getTargetStatement(), args);
        }

        String dbType = statementProxy.getConnectionProxy().getDbType();
        if (CollectionUtils.isEmpty(sqlRecognizers)) {
            sqlRecognizers = SQLVisitorFactory.get(
                    statementProxy.getTargetSQL(),
                    dbType);
        }
        Executor<T> executor;
        if (CollectionUtils.isEmpty(sqlRecognizers)) {
            executor = new PlainExecutor<>(statementProxy, statementCallback);
        } else {
            if (sqlRecognizers.size() == 1) {
                SQLRecognizer sqlRecognizer = sqlRecognizers.get(0);
                switch (sqlRecognizer.getSQLType()) {
                    case INSERT:
                        executor = EnhancedServiceLoader.load(InsertExecutor.class, dbType,
                                new Class[]{StatementProxy.class, StatementCallback.class, SQLRecognizer.class},
                                new Object[]{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;
                }
            } else {
                executor = new MultiExecutor<>(statementProxy, statementCallback, sqlRecognizers);
            }
        }
        T rs;
        try {
            rs = executor.execute(args);
        } catch (Throwable ex) {
            if (!(ex instanceof SQLException)) {
                // Turn other exception into SQLException
                ex = new SQLException(ex);
            }
            throw (SQLException) ex;
        }
        return rs;
    }

    既然是模板那就看看把大象装冰箱的步骤,1、如果不存在全局锁并且不是AT分支事务类型就执行执行StatementCallback,2、获取SqlRecognizer,3、根据不同sql类型获取Executor(将请求封装成命令),4、由Executor执行。再看看UpdateExecutor。

     简单浏览过这几个对象后,关键逻辑在AbstractDMLBaseExecutor中,看看它。

protected T executeAutoCommitFalse(Object[] args) throws Exception {
        if (!JdbcConstants.MYSQL.equalsIgnoreCase(getDbType()) && isMultiPk()) {
            throw new NotSupportYetException("multi pk only support mysql!");
        }
        TableRecords beforeImage = beforeImage();
        T result = statementCallback.execute(statementProxy.getTargetStatement(), args);
        TableRecords afterImage = afterImage(beforeImage);
        prepareUndoLog(beforeImage, afterImage);
        return result;
    }

   这里看来先在执行前获取TableRecords,然后执行sql,然后获取执行后TableRecords,最后把前后的TableRecords用来构建undoLog,然后保存到ConnectionProxy中。

   先说几句题外话,在做spring的本地事务时也就是TransactionIntercepter,我们知道最终TransactionManager,事务的提交落到了Connection上,再说mybatis,事务的提交通过SqlSession、Executor、Transaction最终也要落到Connection上,这个其实不难理解,Connection就是和数据库最直接的连接,或者说jdbc规范就是对数据库客户端的规范(数据层最底层的API),所以任何其它框架要进行事务的操作最终都要交给jdbc的API。

   在上面的内容里我们看到,业务逻辑sql执行完会把undoLog保存到ConnectionProxy中,而提交本地事务时会把undoLog也提交写数据库,然后把undosql和业务sql一起真正提交。

ConnectionProxy

    这里看到ConnectionProxy提交事务的时候大致3步,注册、刷盘undo日志、本地事务提交,注册会通过RM向TC发起BranchRegisterRequest,刷盘undo日志会通过UndoLogManager将undo记录落库。

 当然还有个report,这个是要向TC发起BranchReportRequest。

   说到这里RM差不多说完了,回顾一下RM都在做些什么,当业务sql执行时会同时创建undolog,当事务提交时业务sql和undosql一起提交。当然还有一点,就是在RMClient实例化时会做些事情。

RMNettyRemotingClient
AbstractNettyRemotingClient

     可以看到一个是注册了一些处理器,另一个就是定时的和TC连接发RegisterRMRequest。当然还有二阶段提交,即undo日志的删除,这个很容易不多说了。

总结

    综上RM的核心就是通过对DataSource的代理,来对jdbc全面代理,在sql执行时同时生成undolog,并与sql一起提交,其中通信依托核心模块rpc进行通信。

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

&一步

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值