Seata使用AT模式及源码解析(二)

五.源码启动seata服务

1.修改registry.conf配置

修改registry.conf,配置registry和config都使用nacos形式

2.启动seata服务

3.无法启动解决

  • seata在1.3.0版本中maven版本需要在3.6.0及以上
  • 项目构建无法找到io.seata.serializer.protobuf.generated下的包idea需要安装Protobuf Support插件安装时可能会搜索不到需要通过zip包安装

  • 在seata父pom.xml可能会包有些jar包无法下载需要手动下载jar手动安装到maven本地库mvn install:install-file -DgroupId=com.baidu -DartifactId=ueditor -Dversion=1.0.0 -Dpackaging=jar -Dfile=xxxx.jar
  • 插件安装完毕pom不报错后需要通过插件编译生成不存在的java类运行protobuf:compile,编译所有.proto文件

编译好后,会生成对应的java文件

4.运行server后,seata成功启动注册到nacos上这时在启动三个订单服务事务依旧可以解决

六.源码解析

对于seata源码的研究主要看seata如何拦截业务SQL生成undo_log数据如何在一阶段完成后提交全局事务如何在一阶段业务失败后通过undo_log回滚事务进行事务补偿

seata也是与spring整合使用的结合SpringBootseata也是做了一些自动配置

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初始化总结

  • 启动定时器,尝试进行一次重连seata-server
  • 重连时,先从nacos或则其他配置中根据分组名称(service_group)找到集群名称(cluster_name)
  • 再根据集群名称找到集群ip端口列表
  • 从ip列表中选择一个用netty进行连接

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基本一致

总结

  • Spring启动时,初始化了2个客户端TmClient、RmClient
  • TmClient与seata-server通过Netty建立连接并发送消息
  • RmClient与seata-server通过Netty建立连接,负责接收二阶段提交、回滚消息并在回调器(RmHandler)中做处理

3.AT一阶段开启全局事务

在需要进行全局事务管理的接口上会加@GlobalTransactional注解这个注解会又一个对应的拦截器进行拦截GlobalTransactionalInterceptor,invoke就是拦截方法

  • 如果方法上有全局事务注解,调用handleGlobalTransaction开启全局事务
  • 如果方法上有全局锁注解,调用handleGlobalLock开启全局
  • 如果啥都没有,按普通方法执行,提升性能

handleGlobalTransaction调用了transactionalTemplate.execute方法

  • 开启全局事务beginTransaction
  • 执行业务方法business.execute()
  • 出现异常执行completeTransactionAfterThrowing回滚
  • 没有异常提交事务commitTransaction

开启全局事务最终调用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,提升性能
  • 调用SQLVisitorFactory对目标sql进行解析
  • 针对特定类型sql操作(INSERT,UPDATE,DELETE,SELECT_FOR_UPDATE)等进行特殊解析
  • 执行sql并返回结果

不同类型的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_logseata在删除分支及全局事务

之前说过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模式源码解析完毕。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值