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进行提交通信。
今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。
本文详细分析了Seata AT模式下,RM(Resource Manager)如何通过DataSourceProxy代理数据源,处理RPC调用并执行SQL。在执行过程中,Seata进行SQL解析,根据操作类型创建不同执行器,并在执行前进行镜像查询,准备回滚日志。执行器在提交时会涉及全局事务的XID,以及处理锁冲突的LockRetryPolicy。整个流程揭示了Seata如何实现分布式事务的自动化管理。
912

被折叠的 条评论
为什么被折叠?



