Seata-AT模式的执行流程:
@GlobalTransactional(name = "fsp-create-order",timeoutMills = 30000,rollbackFor = Exception.class)
public void create(Order order) {
LOGGER.info("------->交易开始");
//本地方法
orderDao.create(order);
// 远程方法 扣减库存
storageApi.decrease(order.getProductId(),order.getCount());
//int i = 1/0;
//远程方法 扣减账户余额
LOGGER.info("------->扣减账户开始order中");
accountApi.decrease(order.getUserId(),order.getMoney());
LOGGER.info("------->扣减账户结束order中");
LOGGER.info("------->交易结束");
}
- order-sevice执行创建订单时候,方法上使用@GlobalTransactional注解会在启动中通过GlobalTransactionScanner类生成代理对象。
- 执行create方法前,代理对象会通过TM向seata-server发送全局事务的begin请求,seata-server收到请求生成XID,插入global_table中记录,返回XID。
- 执行orderDao.create(order)方法,会通过RM向seata-server中注册一个事务分支,seata-server记录分支插入branch_table表,返回branchId,然后生成undo_log记录,插入本地数据库,和业务处理一起提交本地事务。
- storageApi.decrease(order.getProductId(),order.getCount())执行,会使用seata的SeataLoadBalancerFeignClient发起请求,会把XID放入header中传递。
- 后续操作都一样 ,RM向seata-server中注册,然后插入branch_table表,返回branchId,然后生成undo_log记录,插入本地数据库,和业务处理一起提交本地事务。
二阶提交:
- 收到 TC 的分支提交请求,把请求放入一个异步任务的队列中,马上返回提交成功的结果给 TC。
- 异步任务阶段的分支提交请求将异步和批量地删除相应 UNDO LOG 记录。
二阶回滚:
- 收到 TC 的分支回滚请求,开启一个本地事务,执行如下操作。
- 通过 XID 和 Branch ID 查找到相应的 UNDO LOG 记录。
- 数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理,详细的说明在另外的文档中介绍
- 根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:
- 提交本地事务。并把本地事务的执行结果(即分支事务回滚的结果)上报给 TC。
我们先来看看Seata-AT模式中,如何向seata-server中发起一个全局事务beign请求,先看到GlobalTransactionScanner如何生成代理对象的。
这里我们看到SpringBoot的自动装配GlobalTransactionAutoConfiguration类
配置了GlobalTransactionScanner,我们看看GlobalTransactionScanner类
我们看到GlobalTransactionScanner继承了AbstractAutoProxyCreator类,这里不详解这个AbstractAutoProxyCreator的作用(遍历Spring管理的类,找到满足你条件的类,进行增强)
AbstractAutoProxyCreator重写了wrapIfNecessary方法
- 判断这个类方法上是否有@GlobalTransactional注解
- 增强的类GlobalTransactionalInterceptor ,所以我们方法上有@GlobalTransactional注解会执行GlobalTransactionalInterceptor 类的invoke方法,所以我们在执行order-service的create方法时,会使用GlobalTransactionalInterceptor进行增强,我们执行create的方法,执行该类的invoke方法,我们去看看
invoke方法中,获取该方法上注解,我们这里执行handleGlobalTransaction(methodInvocation, globalTransactionalAnnotation)方法
执行我们的方法前,会先执行transactionalTemplate.execute 方法
上面会通过XID查看当前是否有GlobalTransaction,如何没有就创建一个新的,重点看
beginTransaction方法
都是调用,继续往下追踪
注意:(这里GlobalBeginRequest类里面有个getTypeCode()方法比较重要,后续seata-server会通过getTypeCode的返回值找到对应的处理器,我们后续在细说)
继续追踪syncCall方法
这里创建了一个TMClient实例,继续看方法sendMsgWithResponse方法
继续追踪
loadBalance方法会去注册中心,拿到seata-server地址(这里不详细说明了)
clientChannelManager.acquireChannel 这里拿到请求地址去请求连接(这里使用的时netty进行连接)
我们继续看重点
构造RpcMessage 消息,进行后续发送
继续追踪
通过netty的channel.writeAndFlush(rpcMessage) 发送消息,客户端发送消息就完成了,我们现在来看看seata-server如何处理的
seata-server执行:
启动Server的main方法
我们看到init方法
registerProcessor 该方法时注册处理器(我们后续在细看,暂时我们看netty如何启动的)
super.init 进入,继续分析
我们这里看到 serverBootstrap.start()方法执行
我们这里看到心跳,编码,解码,还有一个channelHandlers,我们看看这个channelHandlers是什么
Server类main方法中NettyRemotingServer创建对象默认new了一个ServerHandler对象,我们来看看这个对象
我们seata-server中处理请求的handler就是这个,前面我们TMClient向channel通道中写入了一条RpcMessage消息,这里我们会读取到,我们看看seata-server读取到消息以后如何处理的
我们这里,拿到消息 通过消息的body(这里的body就是前面的GlobalBeginRequest对象)来找到对应的处理器,我们这里回过头来,我们前面说的消息请求是GlobalBeginRequest,我们看看getTypeCode()返回的是什么
我们看到getTypeCode返回的是一个枚举MessageType.TYPE_GLOBAL_BEGIN,前面说的registerProcessor方法注册处理器,我们现在来看看
这里把对应的类型的处理器进行注册,我们看看MessageType.TYPE_GLOBAL_BEGIN是ServerOnRequestProcessor处理器,所以我们的消息会执行ServerOnRequestProcessor.process方法进行处理消息,我们去看看
继续追踪
执行GlobalBeginRequest的handle方法
这里执行doGlobalBegin方法
这里执行core.begin方法,把返回的XID设置到response对象中,我们继续看core.begin方法执行
创建GlobalSession执行begin()方法(GlobalSession.createGlobalSession()方法中会生成XID,XID生成规则是IP地址+ 端口号+ UUID生成的transactionId)
执行lifecycleListener.onBegin方法
执行addGlobalSession方法
执行writeSession方法
执行transactionStoreManager.writeSession(logOperation, sessionStorable)方法(这里是根据配置规则来,我配置的DB,还可以配置File,和Redis,配置Redis就插入Redis,配置DB就插入DB)
我们这里是LogOperation.GLOBAL_ADD类型,所以执行insertGlobalTransactionDO方法
这里会根据配置的DB选择不同的sql语句去插入,我配置的是Mysql
拿到sql语句去执行插入操作,这里seata-server就完成了global_table表的操作,插入完成返回XID
客户端拿到XID通过ThreadLocal绑定到本地线程,TM注册的一个过程就完成了。(太长了 后续的操作我们解析二在来说明)