傅青阳眯了一下眼睛,心想这个手下飘了,不给点颜色看看是不行了:talk is cheap show me the code。从AT模式的源码来开始讲整个流程。
元始背转身,负手而立,白衣飘飘,一副胸有成竹的酷拽模样。右手一挥:
我先讲AT模式的源码,然后再看TCC模式、XA模式,最后才是Saga模式,因为AT、TCC、XA这3个模式基本上是使用同一套分布式框架基础组件完成的,而Saga是基于状态机引擎完成的,底层原理不一样。
画外音:
阅读源码之前需要先下载Seata的最新源码,我们使用的是v1.4.2版本;然后下载Seata的官方例子。最后为了调试方便,还需要将官方例子中的一些典型例子代码复制到源码中,作为源码的一个模块,这样在调试的时候就能直接进入到Seata源码中进行断点了,而且还能写注释。
比如我们阅读AT模式源码的时候,可以将seata-samples中的典型例子springboot-dubbo-seata拷贝到seata源码中。拷贝例子需要注意将例子的parent设置为seata-parent。
然后本地需要准备的环境有:
JDK1.8、maven3.8.1、IDEA、MySQL安装教程、Nacos安装教程、Seata Server可以直接解压执行。
如果遇上源码编译的问题,参考这个文档
最后将springboot-dubbo-seata这个demo运行起来。
元始双手缓缓向上托起,一块34寸透明的曲面显示器凭空浮现,叮咚一声,经典的暗黑色的IDEA界面就开始打开:在开始看AT模式的源码之前,先回顾一下AT模式主要实现的两个任务:
- 获取全局锁、开启和提交/回滚全局事务。
- 解析SQL并写入undolog。
然后在Demo的代码,我发现跟Seata框架相关的代码有以下几个地方:
- 2个配置文件:file.conf、registry.conf。等分析完主干流程后再来分析配置文件,会更加清晰明了。
@GlobalTransactional
注解:这个是全局事务的入口。DataSourceProxy
:数据源代理类。GlobalTransactionScanner
:全局事务扫描组件。
那我就从这些地方开始讲起。
GlobalTransactionalInterceptor 全局事务拦截器
这里我们先看看全局事务的入口处理。@GlobalTransactional
这个注解的业务逻辑实现是在GlobalTransactionalInterceptor
这个拦截器。看一下主入口代码:
/**
* 全局事务的入口
*/
@Override
public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
//......
if (specificMethod != null && !specificMethod.getDeclaringClass().equals(Object.class)) {
//...
if (!localDisable) {
// 全局事务处理
if (globalTransactionalAnnotation != null) {
return handleGlobalTransaction(methodInvocation, globalTransactionalAnnotation);
} else if (globalLockAnnotation != null) {
return handleGlobalLock(methodInvocation, globalLockAnnotation);
}
}
}
return methodInvocation.proceed();
}
实际处理在handleGlobalTransaction
方法,继续跟进看看:
Object handleGlobalTransaction(final MethodInvocation methodInvocation,
final GlobalTransactional globalTrxAnno) throws Throwable {
boolean succeed = true;
try {
return transactionalTemplate.execute(new TransactionalExecutor() {// 模板模式应用
//......
});
} //......
}
这里我们先只看主干流程,其他的细节暂时先忽略。这里可以看到就是执行了transactionalTemplate.execute
这个模板方法,我们继续跟进看看。
public Object execute(TransactionalExecutor business) throws Throwable {
//......
// 事务的传播属性
Propagation propagation = txInfo.getPropagation();
SuspendedResourcesHolder suspendedResourcesHolder = null;
try {
switch (propagation) {
//......
case REQUIRED:
break;
//......
}
//......
try {
// 开启全局事务
beginTransaction(txInfo, tx);
Object rs;
try {
// Do Your Business 执行业务逻辑
rs = business.execute();
} catch (Throwable ex) {
// 3. The needed business exception to rollback.
// 执行全局事务回滚
completeTransactionAfterThrowing(txInfo, tx, ex);
throw ex;
}
// 4. everything is fine, commit.
// 执行全局事务提交
commitTransaction(tx);
return rs;
} finally {
//......
}
} //......
}
从上面的代码我们可以很清晰地看到执行的主干流程,就是先开启全局事务,然后在try中执行业务逻辑,如果一切顺序就提交全局事务,如果出现异常就执行全局事务回滚。
傅青阳紧接着问道:那数据源如何管理,事务的回滚肯定需要数据源的配合。
元始:老大放心,数据源是用代理类DataSourceProxy
实现的,接下去看。
DataSourceProxy 数据源代理类
从 DataSourceProxy
这个数据源代理跟进代码,可以发现里面又提供了连接代理ConnectionProxy
:
/**
* 数据库连接
*/
@Override
public ConnectionProxy getConnection() throws SQLException {
Connection targetConnection = targetDataSource.getConnection();
return new ConnectionProxy(this, targetConnection);
}
从这里我们可以大胆猜测一下,因为事务的提交和回滚都是通过Connection.commit
和Connection.rollback
实现的,因此在连接代理类ConnectionProxy
里面肯定会对事务的提交和回滚增加一些Seata框架的处理,比如在本地事务提交之前生成DML语句的前后镜像数据,全局事务提交之后清理本地数据库的undolog数据,本地事务如果回滚则将前镜像数据恢复回来就行了。
说到这里,元始又甩出了一块白板,三两下画了一张图来总结一下:
傅青阳内心鄙视了一下,花里胡哨的东西真多:你这里的全局事务这边跟数据源代理那边还没串联起来吧。
元始:别急啊,我先喝口可乐润润。