1.修改registry.conf配置
修改registry.conf,配置registry和config都使用nacos形式
2.启动seata服务
3.无法启动解决
编译好后,会生成对应的java文件
4.运行server后,seata成功启动,注册到nacos上,这时在启动三个订单服务,事务依旧可以解决
对于seata源码的研究主要看seata如何拦截业务SQL生成undo_log数据,如何在一阶段完成后提交全局事务,如何在一阶段业务失败后通过undo_log回滚事务,进行事务补偿
seata也是与spring整合使用的,结合SpringBoot,seata也是做了一些自动配置
SeataAutoConfiguration就是seata自动配置类,发现其中向Spring容器添加了一个GlobalTransactionScanner Bean,这个就是源码的入口了
GlobalTransactionScanner 实现了InitializingBean, ApplicationContextAware,DisposableBean三个接口,熟悉Spring的知道,InitializingBean提供afterPropertiesSet方法表示bean初始化完成后调用此方法做一些初始化操作,ApplicationContextAware提供setApplicationContext方法通过此方法会向bean中注入ApplicationContext上下文对象,DisposableBean提供destroy方法是bean销毁调用的方法。
在afterPropertiesSet方法中最终调用initClient()方法
里面对TmClient,RmClient进行了初始化(参数就是配置文件里配置的applicationId和txServiceGroup),并注册了一个Spring的ShutdownHook钩子函数
1.TMClient的初始化
启动了一个定时器不断进行重连操作,调用clientChannelManager.reconnect方法进行重连
根据transactionServiceGroup获取seata-server的ip地址列表,然后进行重连
RegistryFactory.getInstance().lookup(transactionServiceGroup);是对不同注册中心做了适配的,默认看下Nacos形式的实现
先根据事务分组找到分组所属的server集群名称,这里是default,然后根据集群名称找到server对应ip端口地址
Seata-server的IP地址已获取到
最后将获取到的seata-server的IP地址放到Netty中封装,TmClient就初始化完毕
TmClient初始化总结:
2.RMClient初始化
设置资源管理器resourceManager,设置消息回调监听器用于接收TC在二阶段发出的提交或者回滚请求,Seata中对ResourceManager,AbstractRMHandler做了SPI适配,以ResouceManager为例:
可以看到初始化DefaultResouceManager时会使用ClassLoader去加载对应Jar下的实现,而默认AT模式使用的实现是数据库,也就是rm-datasource包下的实现,找实现类路径需要定位到/resources/META-INF/扩展接口全路径去找,就会找到对应的实现类
ResourceManager对应实现类全路径 io.seata.rm.datasource.DataSourceManager,该类中指定了了提交和回滚的方法,DefaultRMHandler对应实现类全路径io.seata.rm.RMHandlerAT,是个接收server消息并做对应提交或者回滚操作的回调处理类
RMClinet的init()方法与TMClient基本一致
总结:
3.AT一阶段开启全局事务
在需要进行全局事务管理的接口上,会加@GlobalTransactional注解,这个注解会又一个对应的拦截器进行拦截GlobalTransactionalInterceptor,invoke就是拦截方法
handleGlobalTransaction调用了transactionalTemplate.execute方法
开启全局事务最终调用io.seata.tm.api.DefaultGlobalTransaction#begin(int, java.lang.String)方法
请求seata-server获取全局事务XID
将XID绑定在RootContext中,由此可以看出全局事务是由TM发起的,TM发起全局事务请求给seata-server服务,seata-server服务接受到请求后处理(以下是seata服务代码):
io.seata.server.coordinator.DefaultCoordinator#doGlobalBegin方法接受客户端开启全局事务的请求,调用io.seata.server.coordinator.DefaultCore#begin开启全局事务
通过当前会话开启
实则调用io.seata.server.session.AbstractSessionManager#onBegin方法,又调用io.seata.server.storage.db.session.DataBaseSessionManager#addGlobalSession方法
这里往数据库里写入数据
这里向seata库global_tab插入数据,到此全局事务已开启
4.AT一阶段执行业务SQL
全局事务已开启,下面需要执行业务SQL,生成undo_log数据,全局事务拦截成功后最终还是执行了业务方法的,但是由于Seata对数据源做了代理,所以sql解析与undo_log入库操作是在数据源代理中执行的,代理就是Seata对DataSource,Connection,Statement做的代理封装类
项目中使用的数据源均用seata的DataSourceProxy代替
最终对Sql进行解析操作,发生在StatementProxy类中
不同类型的SQL处理方法不一样,这里以insert为例
insert使用的是InsertExecutor.execute方法,但其实最终还是使用io.seata.rm.datasource.exec.BaseTransactionalExecutor#execute方法
将上下文中的xid绑定到了statementProxy中,并调用了doExecute方法,看下AbstractDMLBaseExecutor中的doExecute方法
方法中调用了executeAutoCommitTrue/executeAutoCommitFalse
但仔细发现,最终都是调用executeAutoCommitFalse方法
获取beforeImage数据
执行业务sql还是使用com.alibaba.druid.pool.DruidPooledPreparedStatement#execute方法执行
获取afterImage
在提交事务是,插入undo_log日志
提交事务,向seata-server注册分支信息,seata-server接收到请求(seata源码)
io.seata.server.coordinator.DefaultCoordinator#doBranchRegister方法
io.seata.server.storage.db.session.DataBaseSessionManager#addBranchSession方法
Seata-server添加分支信息完成,到这里,一阶段结束,业务数据,undo_log,分支信息都已经写入数据库
5.AT二阶段提交
commitTransaction(tx);跟进
最终通过TM请求seata-server,Seata-server接收到全局提交请求(seata源码)
Seata-server接收到客户端全局提交请求后,先回调客户端,删除undo_log,seata在删除分支及全局事务
之前说过RMClient在初始化时,设置资源管理器resourceManager,设置消息回调监听器用于接收TC在二阶段发出的提交或者回滚请求
Seata-server删除分支数据及全局事务数据
客户端删除undo_log数据
getResourceManager获取的就是RMClient初始化时设置的资源管理器DataSourceManager
这边只是往一个ASYNC_COMMIT_BUFFER缓冲List中新增了一个二阶段提交的context,但真正提交在AsyncWorker的init()方法
删除Undo_log
6.AT二阶段回滚
二阶段回滚seata-server端代码与二阶段提交类似,这里省略
主要看回滚客户端如何进行事务补偿
最终回滚方法调用的是UndoLogManager.undo(dataSourceProxy, xid, branchId);
执行回滚sql
判断undolog是否存在,存在则删除对应undolog,并一起提交,到此seata的AT模式源码解析完毕。