简介
这篇文章继续谈谈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一起真正提交。
![](https://i-blog.csdnimg.cn/blog_migrate/0eca91bcf7d6dcf0086a3c459ff4b5a9.png)
这里看到ConnectionProxy提交事务的时候大致3步,注册、刷盘undo日志、本地事务提交,注册会通过RM向TC发起BranchRegisterRequest,刷盘undo日志会通过UndoLogManager将undo记录落库。
当然还有个report,这个是要向TC发起BranchReportRequest。
说到这里RM差不多说完了,回顾一下RM都在做些什么,当业务sql执行时会同时创建undolog,当事务提交时业务sql和undosql一起提交。当然还有一点,就是在RMClient实例化时会做些事情。
![](https://i-blog.csdnimg.cn/blog_migrate/a705077ad8e2ba21f7e820e579837c7f.png)
![](https://i-blog.csdnimg.cn/blog_migrate/b65060efcb6b2432a30da5a0b1b81561.png)
可以看到一个是注册了一些处理器,另一个就是定时的和TC连接发RegisterRMRequest。当然还有二阶段提交,即undo日志的删除,这个很容易不多说了。
总结
综上RM的核心就是通过对DataSource的代理,来对jdbc全面代理,在sql执行时同时生成undolog,并与sql一起提交,其中通信依托核心模块rpc进行通信。