seata源码分析之-AT模式客户端分析3
RM的大致执行流程图
RM之StorageService
前面我们分析了springcloud
的例子中的TM
的BusinessService
的一些原理,接下来我们来找个RM
说下,比如说storage
微服务,因为他看起来比较简单,就是一个更新操作:
我们可以看到这个就是一个普通的Feign
的RPC
调用,好像什么都没做,除了GlobalTransactionScanner
干的初始化TM
和RM
之外的事,还有什么呢,那怎么就参与到了全局事务中了,想想应该也是做了什么代理对吧。
DataSourceProxy数据源代理
我们可以在StorageApplication
中看到这些,可以想到我们JDBC
用的数据源被代理了。
DataSourceProxy的一些方法
于是我很好奇的看了下这个类,找了一些方法,好像在创建的时候就会去TC
那里注册分支事务,然后默认是AT
模式:
内部还是用Netty
组件去进行注册,这个具体就不深入了。
处理RPC调用
我们看到很常规的调用过来了,然后进行更新。
一直跟进去到execute
:
主要是这里,可以看到这里的连接已经是代理的了:
里面如果发现是AT
模式,就进行SQL
的解析,封装成PreparedStatementProxy
返回。
最后调用PreparedStatementProxy
的executeUpdate
里面是ExecuteTemplate
的execute
,其实就是做了代理,里面用了一个ExecuteTemplate
:
ExecuteTemplate的execute
首先判断是否需要代理,不需要就直接正常执行了,否则就看数据库类型,找出对应的sql
识别器,对一些操作进行识别,根据不同的操作创建对应的执行器,比如这里是更新的话就是UpdateExecutor
,然后调用执行方法。
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;
}
BaseTransactionalExecutor的execute
调用了父类的执行方法,获取全局事务XID
绑定,这个XID
哪里来的,其实是RPC
调用的时候放在头信息里带过来的,有兴趣的可以研究下SeataHandlerInterceptor
,最终是doExecute
。
SeataHandlerInterceptor
进行了XID
的获取:
默认是自动提交的:
这里ConnectionProxy
进行setAutoCommit
设置的时候做了一些事情。
设置false
的时候先不提交,最后设置true
的时候才提交:
LockRetryPolicy的execute
这个就是为了处理全局锁冲突重试的,其实可以理解为一个分布式锁。
可以看到,他会进行重试,然后睡眠一定时间,一定次重试后就会抛出异常。
镜像查询与回滚日志插入
然后调用要执行的方法executeAutoCommitFalse
:
这里主要是进行前后镜像查询,准备回滚日志信息,为什么要进行前后镜像查询了,其实就是为了回滚的时候可以判断要回滚或者是否有脏写,如果回滚的时候发现现在数据和前镜像一样,其实就不需要回滚,如果发现和后镜像一样,那就要回滚,如果发现不一样,那说明有其他事务可能修改了,那可能要手动处理了:
我们看看他在准备回滚日志的时候做了什么,可以看到生成了一个锁,其实就是进行隔离写的行锁,格式是表名_主键值,将回滚信息放入ConnectionProxy
中:
然后回滚信息里有前后镜像的信息:
然后进行提交,其实还没有真正提交,还要做一些事情:
最终调用ConnectionProxy
的processGlobalTransactionCommit
:
好了,篇幅有点长了,后面再讲全局事务提交方法做了什么。
总结下这篇的东西:
RM
对数据源,连接,处理器进行了代理- 会对
sql
进行解析,根据数据源类型和操作类型生成不同的执行器。 - 执行器进行提交前会进行前后镜像的查询,然后执行
sql
语句,创建回滚日志,准备和TC
进行提交通信。
今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。