1 应用背景和束缚
公司应用的是springcloud,面临分布式事务的场景的时候,能够应用对springcloud反对比拟好的byte-tcc框架,git目前2600星,应用起来也十分不便,原理也很清晰,非常适合学习。https://github.com/liuyangmin...,联合cloud有几个重点束缚如下,
(1)一个业务接口,须要有三种实现类,别离是try,confirm,cancel,合乎tcc的思路。实现办法必须加Transactional,且propagation必须是Required, RequiresNew, Mandatory中的一种。
(2)服务提供方Controller必须增加@Compensable注解,不容许对Feign/Ribbon/RestTemplate等HTTP申请自行进行封装。想想看为什么?
(3)在每个参加tcc事务的数据库中创立bytejta表。
配置上也非常简单,@Import(SpringCloudConfiguration.class),服务提供方,try下面加上
@Service("accountService") @Compensable( interfaceClass = IAccountService.class , confirmableKey = "accountServiceConfirm" , cancellableKey = "accountServiceCancel" )
confirm和cancel对应的
@Service("accountServiceConfirm")@Service("accountServiceCancel"),
try confirm cancel 对应业务能够了解为 解冻库存/真正扣减库存/复原库存,或者解冻优惠券/核销优惠券/复原优惠券这种理论业务场景。
0.5当前datasource主动应用LocalXADataSource,之前须要手动配置
@Bean(name = "dataSource") public DataSource getDataSource() { LocalXADataSource dataSource = new LocalXADataSource(); dataSource.setDataSource(this.invokeGetDataSource()); return dataSource; }
所以,从配置上看,bytetcc和springcloud联合,一个应该是通过引入本人的SpringCloudConfiguration封装了feign/ribbon/hystrix调用,一个是提供了本人的datasource治理事务。有了本人的datasource,定制本人的transactionManager,就能够在事务前后动手脚,2pc/3pc对事务的治理,体现在管制不同数据库连贯上,微服务为主的tcc,对事务的治理体现在管制各个服务的调用上。
2 业务场景思考
bytetcc的tcc,其实try和cancel是配套的,思考下业务场景:
(1)如果a服务try胜利了,b服务try失败,则a服务须要回滚,调用a的cancel。这是广泛流程。
(2)如果a 和 b都try胜利了,而后a confirm胜利,b的confirm失败,是没有cancel和confirm配对的。b的confirm会一直调用直到胜利为止。
因为bytetcc的设计思路是,通过try做好筹备工作(如锁定资源),try如果能胜利,那么逻辑上confirm肯定要胜利。如果confirm不胜利,则可能是外部环境问题,如网络问题等,那么环境复原了迟早应该胜利confirm。基于这个思维,try和cancel是互逆的,confirm一旦执行就不可逆。
如果要设计confirm也可逆的,那要么cancel里判断是该回滚try还是回滚try+confirm,不清晰且实现很麻烦,或者做成“tccc”加一个对应confirm的cancel,由事务管理器对立判断调用几个cancel,引入太多不确定。所以业务上能够间接这么设计:try胜利,那么confirm是肯定要胜利的。
3 外围组件SpringCloudConfiguration原理剖析
第一步就import的SpringCloudConfiguration是重点,通过它的各种主动拆卸根本能够实现bytetcc的全逻辑。要想扩大spring,那就得扩大各种BeanFactoryPostProcessor,SpringCloudConfiguration自身就是个BeanFactoryPostProcessor,然而postProcessBeanFactory没干啥,应该是引入了各种其余processor进行扩大。如何扩大面临几个问题
(1) 如何辨认外围的@Compensable注解?
在byte-tcc的一堆resource文件里,配置了各种bean。既然都Springcloud了为啥还用这种文件bean,可能是兼容cloud之外的场景,加强通用性。在bytetcc-supports-tcc.xml里,有个bean <bean class="org.bytesoft.bytetcc.supports.spring.CompensableAnnotationConfigValidator" />,通过要害代码
clazz.getAnnotation(Compensable.class);扫描失去所有注解了Compensable的类,同时解析进去cancel,confirm对应的类。同时校验一下有没有加transactional注解。前面很多相似的processor都是用这种注解辨认须要的bean
(2)如何革新事务管理器,使之适应散布式微服务环境?
在bytetcc-supports-tcc.xml中,定义了革新过的transactionManager,
<bean id="transactionManager" class="org.bytesoft.bytetcc.TransactionManagerImpl" />
这外面重写了begin,commit那一套,联合了tcc专用组件CompensableManager,从新包装了事务操作。
同时,通过SpringCloudConfiguration配置的CompensableHandlerInterceptor,达到transactionInterceptor的成果。
(3)事务如何复原?
在bytetcc-supports-logger-primary.xml中,有个ResourceAdapterImpl,这个是启动bytetcc后盾线程的中央,看bean配置就晓得,把两个bean compensableWork和bytetccCleanupWork注入到ResourceAdapterImpl中对立治理,字面意思上看是弥补工作和数据清理工作。这里的compensableWork就是弥补和数据恢复专用的job。
<bean id="compensableResourceAdapter" class="org.bytesoft.transaction.adapter.ResourceAdapterImpl"> <property name="workList"> <list> <ref bean="compensableWork" /> <ref bean="bytetccCleanupWork" /> </list> </property> </bean>
追进去看,通过List<Work> workList收集起来work,而后对立用mananger进行start,看work对象自身就继承了Runnable,所以这里是开启了后盾线程,进行事务复原等操作。
(4)具体的申请接口,怎么扩大?
import的SpringCloudConfiguration的代理组件,通过条件注解和配置,依据是否开启hystrix和是否引入HystrixFeign的类,注入针对feign或hystrix的CompensableFeignBeanPostProcessor,如下
@org.springframework.context.annotation.Bean @ConditionalOnProperty(name = "feign.hystrix.enabled", havingValue = "false", matchIfMissing = true) public CompensableFeignBeanPostProcessor feignPostProcessor() { return new CompensableFeignBeanPostProcessor(); } @org.springframework.context.annotation.Bean @ConditionalOnProperty(name = "feign.hystrix.enabled") @ConditionalOnClass(feign.hystrix.HystrixFeign.class) public CompensableHystrixBeanPostProcessor hystrixPostProcessor() { return new CompensableHystrixBeanPostProcessor(); }
以CompensableFeignBeanPostProcessor为例,显著这就是为了对feign接口进行代理的PostProcessor,在postProcessAfterInitialization中,果然通过createProxiedObject(),创立了CompensableFeignHandler的代理类,对springcloud本人的FeignInvocationHandler进行了又一次代理。这样所有@FeignClient的接口都会通过这个handler
同理如果是hystrix的代理,CompensableHystrixBeanPostProcessor会创立CompensableHystrixFeignHandler代理,代替原来的CompensableHystrixInvocationHandler。
通过这两种代理,能够对原生的feign/hystrix组件持续代理,在申请前后做些事件。feign/hystrix其实自身也是联合了ribbon的代理,所以很多spring的扩大就是一层层的代理叠加,为咱们扩大组件提供了一种思路。那么bytetcc的外围流程必定就蕴含在这个申请代理中。
(5)如何管制申请哪一种办法?
bytetcc-supports-springcloud-primary.xml中,有个controller,CompensableCoordinatorController,能够看到外面封装了几种办法,prepare,commit,rollback,额定还有recover,forget,名字上能够看出是复原,删除事务。联合第四点,原来的feign调用被代理一层,申请的实在url应该被改过,改成了申请这一个controller的办法,通过这个controller再决定前面做什么。
@RequestMapping(value = "/org/bytesoft/bytetcc/prepare/{xid}", method = RequestMethod.POST)@RequestMapping(value = "/org/bytesoft/bytetcc/commit/{xid}/{opc}", method = RequestMethod.POST)@RequestMapping(value = "/org/bytesoft/bytetcc/rollback/{xid}", method = RequestMethod.POST)@RequestMapping(value = "/org/bytesoft/bytetcc/recover/{flag}", method = RequestMethod.GET)@RequestMapping(value = "/org/bytesoft/bytetcc/forget/{xid}", method = RequestMethod.POST)
综上所述,通过新的分布式事务管理器的封装,feign/hystrix申请的代理,controller的管制,后盾弥补工作的执行,基本上能够实现强一致性的分布式事务。
4 事务启动-try过程
(1)产生事务
接到用户一个申请时, CompensableHandlerInterceptor会先拦挡,这是用户刚发的申请,在这里没找到事务信息什么都不干就返回true了,如果是被调用者,无论是try/confirm/cancel,都会有个事务上下文信息,解析出事务。
CompensableMethodInterceptor->excute(),取得了@transactional和@composable注解,包含 confirm/cancel办法信息,封装到invocation,保留本次调用的一些信息。
transactionInterceptor,调用bytetcc提供的TransactionManagerImpl,提供了新的begin,启动tcc事务。留神这里,如果没有事务上下文,没有compensable注解,那就走个别的begin,就是个别的本地事务。
有了compensable注解,begin就是下面说到的CompensableManager的compensableBegin办法,初始化了事务上下文环境transanctionContext,还生成了个事务id-xid。
(2)办法执行,CompensableFeignInterceptor,把下面生成的事物上下文环境transactionContext,通过序列化生成字符串,放入request的header中,这样发动事务无论调用什么服务,事务的上下文信息就保留在request的header里了。
前面依据feign的代理CompensableFeignHandler发出请求,return this.delegate.invoke(proxy, method, args) delegate就是feign,实质上也是应用原生的feign发申请。
既然实质也是feign调用,思考一下为啥还麻烦代理一次?事务上下文环境在interceptor外面曾经设置到request里了,还代理干啥?
往后看,我认为要害在这里,
beanRegistry.setLoadBalancerInterceptor(new CompensableLoadBalancerInterceptor(this.statefully)
大家晓得,feign通过ribbon组件进行的简单平衡,即chooseInstance,抉择申请往哪个实例上发,如果还是轮训或随机,第一次try申请发到某实例,第二次confirm/cancel发到其余实例,别的实例上没有try带来的的事务信息,会十分不不便,也不晓得try到底什么状况,
所以这里要屡次申请粘滞到一个实例上。所以bytetcc实现了ribbon算法CompensableLoadBalancerRuleImpl,不反对自定义rule
(3)服务接管方承受,首先通过CompensableHandlerInterceptor的preHandle,解析出事务上下文transactionContext,封装成TransactionRequestImpl,并在response里加上一些header信息。这在发起者那里因为没有header的上下文,所以在(1)是什么都不做的
再到 CompensableMethodInterceptor, 解析办法。
再到 TransactionManager,即bytetcc的TransactionManagerImpl,到这里的begin,因为刚接到申请的时候,这时候曾经有事务了,所以调用的是个别的本地事务compensableManager.begin(),最初开启一个本地事务,而后执行本地办法,执行commit。
下图能够简略介绍这个过程。
5 try调用失败,cancel过程
(1)如果有任意一个try失败,那么要把曾经胜利的try给回滚掉,spring通用的transactionInterceptor的处理过程,invokeWithinTransaction办法,如果有异样,catch住执行
completeTransactionAfterThrowing(),而后到transactionManagerImpl的rollback,持续到CompensableManager的collback
(2)CompensableTransanctionImpl中,fireRemoteParticipantCancel是真正的rollback,外面保护了一个resourcelist,按程序记录了其余各个服务在try的时候调用的服务,在这里循环这个list调用SpringCloudCoordinator,拼接cancel地址,带着事务id发送申请过来。
(3)接管方,CompensableCoordinatorController的rollback,外围是从CompensableTransactionImpl到SpringContainerContextImpl 的 cancel,失去申请的controller的对应的cancel办法,封装到cancellableKey,而后拿到解决cancel的实在的bean,
Object instance = this.applicationContext.getBean(cancellableKey);this.cancelComplicated(method, instance, args);
进而执行cancel对应的bean的办法。整个过程能够如下概括
6 try胜利,confirm过程
同理,transactionManagerImpl的commit,最终达到CompensableTransactionImp进行fireCommit,先提交本地事务,而后fireRemoteParticipantConfirm,和cancel截然不同,读取resourceList,遍历list发送申请到各个服务端。
各个服务方CompensableCoordinatorController的commit,拿到confirmablekey,找到confirm的bean进行confirm。
7 “compensable”的弥补
(1)如果cancel,commit有失败(失败蕴含runtimeexception和自定义的一些异样),那么如何进行弥补,下面提到的一开始就启动的CompensableWork线程的run外面,其实有个while(true),每隔100秒循环一次,调用组件TransactionRecovery(看名字就晓得复原事务用的)的timingRecover,就是定时回复,会调用到CompensableTransactionImpl的recoveryRollback/recoveryCommit,还是SpringCloudCoordinator发送的申请。
(2)如果呈现宕机,重启后也是通过CompensableWork线程的run,第一步是init,尝试复原现有的事务。
a 如果try没有执行完就down机,复原时把已执行的try给cancel掉。因为事务个别是业务申请触发的,down机就申请失败了,没必要重启后还复原方才的申请。b 如果是confirm/cancel有没胜利的,会始终定时进行confirm/cancel。