这篇博客仅供我自己复习使用,不保证内容100%正确,使用的是ByteTCC 0.4.x版本,基于Spring Cloud。
github地址: https://github.com/liuyangming/ByteTCC/wiki
反正是写给我自己的,就不去写如何使用了,以后若是想用,直接参考官网提供的Demo即可。
首先,随着项目启动,Spring会读取在启动类上使用的@ImportResource注解,将bytetcc-supports-springcloud.xml中配置的所有Bean创建、导入至Spring容器。ByteTCC自定义了数量众多的类,值得注意的类有:
接着,除了创建对象外,ByteTCC对数据源也进行了封装,其实就是对原生的Datasource做了一个动态代理,所有对原生Datasource的请求会先走LocalXADatasource(由ByteTCC提供),底层再走原生Datasource的方法。(当然了,我们自己也可以做DruidDatasource,套在原生Datasource上,外面再套一层LocalXADatasource) 。
最后,伴随着项目的启动,ByteTCC还创建了一堆后台线程,比较重要的线程有:
接收到外部请求后,主业务服务主要经历了以下几个步骤:
首次接收到请求,这个拦截器并没有什么用,它主要是用来解析header中存放的分布式事务的信息,由于现在是首次接到请求,所以请求中根本就没有分布式事务的信息。
ByteTCC为被@Compensable修饰的类做了动态代理,现在外部请求想要调用这个类的方法,那么请求当然会被拦截。这里其实就是在开启一个分布式事务,创建分布式事务的id,以及分布式事务的上下文(TransactionContext)等等。
被调用的方法肯定会被@Transactional修饰,所以spring-tx肯定会做一个动态代理,用于实现对这个方法的事务管理。TransactionInterceptor 的invoke()方法,老生常谈了,不就是先创建事务,然后执行业务逻辑,有报错执行回滚,没报错事务提交么。这里就是创建了一个本地事务,接着执行业务逻辑。想想看,主业务服务的本地业务逻辑被执行,这个方法内会去调用从业务服务的方法,只要涉及到调用远程服务的方法,必然要走Feign的动态代理,走Ribbon,所以接下来请求一定会被CompensableFeignHandler给拦截下来。
这个方法内代码一大坨,比如重构了负载均衡器,核心代码就一句: this.delegate.invoke(proxy, method, args);delegate就是HardCodedTarget,这玩意不就是Feign底层用来发送http请求的么,请求发送的过程学了很多遍,不赘述了。
但值得注意的是,在真正的发送请求之前,会走CompensableInterceptorImpl的beforeSendRequest( )方法,这里面有一句非常重要的代码
boolean participantEnlisted = transaction.enlistResource(descriptor);
这个方法会创建一个分布式事务的子事务(XAResourceArchive),每一个子事务对应着一个从业务服务的请求调用,最后放入resourceList内。本次主业务服务的方法内,所有对从业务服务的请求调用,都会生成一个子事务,存入resourceList。
这个resourceList非常重要,并且需要注意的是,此时try请求还没有发送出去!
还是要回到CompensableFeignHandlerde的invoke( )。这里面有两个变量: participantEnlistFlag和participantDelistFlag。虽然我没有细看Feign底层发起http请求至从业务服务,解析响应结果,并填入CompensableTransactionImpl的participantEnlistFlag和participantDelistFlag内的具体过程,但是通过打断点,可以大概猜测出含义:
1.participantEnlistFlag
这个东西就是用于标识,本次分布式子事务是否需要加入分布式事务,它是一个布尔值,第一次调用从业务服务的方法时,显然就是true了。加入的地方就是resourceList。
2. participantDelistFlag
这个东西就是用于标识,是否需要从分布式子事务的列表中,移除当前分布式子事务。
我猜测ByteTCC就是用participantDelistFlag这个变量来控制下方的代码,实现是否需要在resourceList中,移除当前分布式子事务。如果从业务服务的try执行失败了,那么participantDelistFlag就等于true,那么就执行remove方法。
这里就是利用participantDelistFlag来移除分布式子事务。比如说,本次请求从业务服务的try方法执行失败了,显然就需要把这个从业务服务对应的分布式子事务从resourceList给移除掉。(写的思路有点儿跳跃,这个地方移除子事务是有用意的,毕竟resourceList剩余的子事务肯定是try执行成功的,现在有一个try执行失败了,那显然剩下来的子事务就可以用来发送cancel请求了)
下方的代码来自CompensableTransactionImpl的delistResource()方法,XAResource.TMSUCCESS代码代表分布式子事务执行成功,XAResource.TMFAIL代表执行失败。
public boolean delistResource(XAResource xaRes, int flag) throws IllegalStateException, SystemException { 省略.. if (RemoteResourceDescriptor.class.isInstance(xaRes)) { 省略.. XAResourceArchive archive = this.resourceMap.get(identifier); if (flag == XAResource.TMFAIL) { this.resourceMap.remove(identifier); if (archive != null) { this.resourceList.remove(archive); } // end-if (archive != null) compensableLogger.updateTransaction(this.getTransactionArchive()); } else { if (archive != null) { this.applicationMap.put(resource.getApplication(), archive); } } } return true; }
如果发现某个从业务服务的try执行出错,主业务服务会执行CompensableTransactionImpl内的方法,无非就是两个步骤:
代码逻辑都很简单,值得注意的就一点,那就是需要回滚哪些从业务服务的事务呢?
这里就需要借助之前一直强调的resourceList了,首先,被调用了try接口的服务都会放到resourceList中,接着会把那些执行报错的从业务服务给移除掉,那么resourceList剩下的从业务服务,一定都是try执行成功的,我们需要回滚的就是这些从业务服务执行的try逻辑。
此时,主业务服务会循环遍历resourceList,取出每一个从业务服务,借助SpringCloudCoordinator(这个东西也是ByteTCC自己写的),通过RestTemplate等组件发送cancel请求至从业务服务。
问:为什么不需要回滚try执行报错的从业务服务的事务呢?
答:由于从业务服务自己的try方法上一定会加@Transactional,所以从业务服务借助JTA自己就可以回滚本地事务了,根本就不需要主业务服务来帮忙。
从业务服务接收请求的controller是ByteTCC提供的,叫做CompensableCoordinatorController,它的执行逻辑也很简单,首先解析请求头,反序列化出分布式事务的具体信息,接着根据启动时扫描获取的@Compensable注解信息,找到cancellableKey属性的值,然后在spring容器内找到对应的类,最后调用对应的方法,完成cancel逻辑。
还没写完…