溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何在springcloud中使用bytetcc實(shí)現(xiàn)數(shù)據(jù)的強(qiáng)一致性

發(fā)布時(shí)間:2021-03-02 16:35:29 來(lái)源:億速云 閱讀:453 作者:Leah 欄目:開發(fā)技術(shù)

今天就跟大家聊聊有關(guān)如何在springcloud中使用bytetcc實(shí)現(xiàn)數(shù)據(jù)的強(qiáng)一致性,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

1 使用背景和約束

公司使用的是springcloud,面臨分布式事務(wù)的場(chǎng)景的時(shí)候,可以使用對(duì)springcloud支持比較好的byte-tcc框架,git目前2600星,使用起來(lái)也非常方便,原理也很清晰,非常適合學(xué)習(xí)。 https://github.com/liuyangmin... ,結(jié)合cloud有幾個(gè)重點(diǎn)約束如下,

(1)一個(gè)業(yè)務(wù)接口,需要有三種實(shí)現(xiàn)類,分別是try,confirm,cancel,符合tcc的思路。實(shí)現(xiàn)方法必須加Transactional,且propagation必須是Required, RequiresNew, Mandatory中的一種。

(2)服務(wù)提供方Controller必須添加@Compensable注解,不允許對(duì)Feign/Ribbon/RestTemplate等HTTP請(qǐng)求自行進(jìn)行封裝。想想看為什么?

(3)在每個(gè)參與tcc事務(wù)的數(shù)據(jù)庫(kù)中創(chuàng)建bytejta表。

配置上也非常簡(jiǎn)單,@Import(SpringCloudConfiguration.class),服務(wù)提供方,try上面加上

@Service("accountService")
 @Compensable(
 interfaceClass = IAccountService.class 
 , confirmableKey = "accountServiceConfirm"
 , cancellableKey = "accountServiceCancel"
 )

confirm和cancel對(duì)應(yīng)的

@Service("accountServiceConfirm")
@Service("accountServiceCancel"),

try confirm cancel 對(duì)應(yīng)業(yè)務(wù)可以理解為 凍結(jié)庫(kù)存/真正扣減庫(kù)存/恢復(fù)庫(kù)存,或者凍結(jié)優(yōu)惠券/核銷優(yōu)惠券/恢復(fù)優(yōu)惠券這種實(shí)際業(yè)務(wù)場(chǎng)景。

0.5以后datasource自動(dòng)使用LocalXADataSource,之前需要手動(dòng)配置

@Bean(name = "dataSource")
 public DataSource getDataSource() {
  LocalXADataSource dataSource = new LocalXADataSource();
  dataSource.setDataSource(this.invokeGetDataSource());
  return dataSource;
 }

所以,從配置上看,bytetcc和springcloud結(jié)合,一個(gè)應(yīng)該是通過引入自己的SpringCloudConfiguration封裝了feign/ribbon/hystrix調(diào)用,一個(gè)是提供了自己的datasource管理事務(wù)。有了自己的datasource,定制自己的transactionManager,就可以在事務(wù)前后動(dòng)手腳,2pc/3pc對(duì)事務(wù)的管理,體現(xiàn)在控制不同數(shù)據(jù)庫(kù)連接上,微服務(wù)為主的tcc,對(duì)事務(wù)的管理體現(xiàn)在控制各個(gè)服務(wù)的調(diào)用上。

2 業(yè)務(wù)場(chǎng)景思考

bytetcc的tcc,其實(shí)try和cancel是配套的,考慮下業(yè)務(wù)場(chǎng)景:

(1)如果a服務(wù)try成功了,b服務(wù)try失敗,則a服務(wù)需要回滾,調(diào)用a的cancel。這是普遍流程。

(2)如果a 和 b都try成功了,然后a confirm成功,b的confirm失敗,是沒有cancel和confirm配對(duì)的。b的confirm會(huì)不斷調(diào)用直到成功為止。

因?yàn)閎ytetcc的設(shè)計(jì)思路是,通過try做好準(zhǔn)備工作(如鎖定資源),try如果能成功,那么邏輯上confirm一定要成功。如果confirm不成功,則可能是外部環(huán)境問題,如網(wǎng)絡(luò)問題等,那么環(huán)境恢復(fù)了遲早應(yīng)該成功confirm?;谶@個(gè)思想,try和cancel是互逆的,confirm一旦執(zhí)行就不可逆。

如果要設(shè)計(jì)confirm也可逆的,那要么cancel里判斷是該回滾try還是回滾try+confirm,不清晰且實(shí)現(xiàn)很麻煩,或者做成“tccc”加一個(gè)對(duì)應(yīng)confirm的cancel,由事務(wù)管理器統(tǒng)一判斷調(diào)用幾個(gè)cancel,引入太多不確定。所以業(yè)務(wù)上可以直接這么設(shè)計(jì):try成功,那么confirm是一定要成功的。

3 核心組件SpringCloudConfiguration原理分析

第一步就import的SpringCloudConfiguration是重點(diǎn),通過它的各種自動(dòng)裝配基本可以實(shí)現(xiàn)bytetcc的全邏輯。要想擴(kuò)展spring,那就得擴(kuò)展各種BeanFactoryPostProcessor,SpringCloudConfiguration本身就是個(gè)BeanFactoryPostProcessor,但是postProcessBeanFactory沒干啥,應(yīng)該是引入了各種其他processor進(jìn)行擴(kuò)展。如何擴(kuò)展面臨幾個(gè)問題

(1) 如何識(shí)別核心的@Compensable注解?

在byte-tcc的一堆resource文件里,配置了各種bean。既然都Springcloud了為啥還用這種文件bean,可能是兼容cloud之外的場(chǎng)景,增強(qiáng)通用性。在bytetcc-supports-tcc.xml里,有個(gè)bean <bean class="org.bytesoft.bytetcc.supports.spring.CompensableAnnotationConfigValidator" />,通過關(guān)鍵代碼

clazz.getAnnotation(Compensable.class);掃描得到所有注解了Compensable的類,同時(shí)解析出來(lái)cancel,confirm對(duì)應(yīng)的類。同時(shí)校驗(yàn)一下有沒有加transactional注解。后面很多類似的processor都是用這種注解識(shí)別需要的bean

(2)如何改造事務(wù)管理器,使之適應(yīng)分布式微服務(wù)環(huán)境?

在bytetcc-supports-tcc.xml中,定義了改造過的transactionManager,

<bean id="transactionManager" class="org.bytesoft.bytetcc.TransactionManagerImpl" />

這里面重寫了begin,commit那一套,結(jié)合了tcc專用組件CompensableManager,重新包裝了事務(wù)操作。

同時(shí),通過SpringCloudConfiguration配置的CompensableHandlerInterceptor,達(dá)到transactionInterceptor的效果。

(3)事務(wù)如何恢復(fù)?

在bytetcc-supports-logger-primary.xml中,有個(gè)ResourceAdapterImpl,這個(gè)是啟動(dòng)bytetcc后臺(tái)線程的地方,看bean配置就知道,把兩個(gè)bean compensableWork和bytetccCleanupWork注入到ResourceAdapterImpl中統(tǒng)一管理,字面意思上看是補(bǔ)償任務(wù)和數(shù)據(jù)清理任務(wù)。這里的compensableWork就是補(bǔ)償和數(shù)據(jù)恢復(fù)專用的job。

<bean id="compensableResourceAdapter" class="org.bytesoft.transaction.adapter.ResourceAdapterImpl">
  <property name="workList">
   <list>
    <ref bean="compensableWork" />
    <ref bean="bytetccCleanupWork" />
   </list>
  </property>
 </bean>

追進(jìn)去看,通過List<Work> workList收集起來(lái)work,然后統(tǒng)一用mananger進(jìn)行start,看work對(duì)象本身就繼承了Runnable,所以這里是開啟了后臺(tái)線程,進(jìn)行事務(wù)恢復(fù)等操作。

(4)具體的請(qǐng)求接口,怎么擴(kuò)展?

import的SpringCloudConfiguration的代理組件,通過條件注解和配置,根據(jù)是否開啟hystrix和是否引入HystrixFeign的類,注入針對(duì)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為例,明顯這就是為了對(duì)feign接口進(jìn)行代理的PostProcessor,在postProcessAfterInitialization中,果然通過createProxiedObject(),創(chuàng)建了CompensableFeignHandler的代理類,對(duì)springcloud自己的FeignInvocationHandler進(jìn)行了又一次代理。這樣所有@FeignClient的接口都會(huì)經(jīng)過這個(gè)handler

同理如果是hystrix的代理,CompensableHystrixBeanPostProcessor會(huì)創(chuàng)建CompensableHystrixFeignHandler代理,代替原來(lái)的CompensableHystrixInvocationHandler。

通過這兩種代理,可以對(duì)原生的feign/hystrix組件繼續(xù)代理,在請(qǐng)求前后做些事情。feign/hystrix其實(shí)本身也是結(jié)合了ribbon的代理,所以很多spring的擴(kuò)展就是一層層的代理疊加,為我們擴(kuò)展組件提供了一種思路。那么bytetcc的核心流程肯定就蘊(yùn)含在這個(gè)請(qǐng)求代理中。

(5)如何控制請(qǐng)求哪一種方法?

bytetcc-supports-springcloud-primary.xml中,有個(gè)controller,CompensableCoordinatorController,可以看到里面封裝了幾種方法,prepare,commit,rollback,額外還有recover,forget,名字上可以看出是恢復(fù),刪除事務(wù)。結(jié)合第四點(diǎn),原來(lái)的feign調(diào)用被代理一層,請(qǐng)求的真實(shí)url應(yīng)該被改過,改成了請(qǐng)求這一個(gè)controller的方法,通過這個(gè)controller再?zèng)Q定后面做什么。

@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)

綜上所述,通過新的分布式事務(wù)管理器的封裝,feign/hystrix請(qǐng)求的代理,controller的控制,后臺(tái)補(bǔ)償任務(wù)的執(zhí)行,基本上可以實(shí)現(xiàn)強(qiáng)一致性的分布式事務(wù)。

4 事務(wù)啟動(dòng)-try過程

(1)產(chǎn)生事務(wù)

接到用戶一個(gè)請(qǐng)求時(shí), CompensableHandlerInterceptor會(huì)先攔截,這是用戶剛發(fā)的請(qǐng)求,在這里沒找到事務(wù)信息什么都不干就返回true了,如果是被調(diào)用者,無(wú)論是try/confirm/cancel,都會(huì)有個(gè)事務(wù)上下文信息,解析出事務(wù)。

CompensableMethodInterceptor->excute(),獲得了@transactional和@composable注解,包括 confirm/cancel方法信息,封裝到invocation,保存本次調(diào)用的一些信息。

transactionInterceptor,調(diào)用bytetcc提供的TransactionManagerImpl,提供了新的begin,啟動(dòng)tcc事務(wù)。注意這里,如果沒有事務(wù)上下文,沒有compensable注解,那就走一般的begin,就是一般的本地事務(wù)。

有了compensable注解,begin就是上面說到的CompensableManager的compensableBegin方法,初始化了事務(wù)上下文環(huán)境transanctionContext,還生成了個(gè)事務(wù)id-xid。

(2)方法執(zhí)行,CompensableFeignInterceptor,把上面生成的事物上下文環(huán)境transactionContext,通過序列化生成字符串,放入request的header中,這樣發(fā)起事務(wù)無(wú)論調(diào)用什么服務(wù),事務(wù)的上下文信息就保存在request的header里了。

后面根據(jù)feign的代理CompensableFeignHandler發(fā)出請(qǐng)求,return this.delegate.invoke(proxy, method, args) delegate就是feign,本質(zhì)上也是使用原生的feign發(fā)請(qǐng)求。

既然本質(zhì)也是feign調(diào)用,思考一下為啥還費(fèi)事代理一次?事務(wù)上下文環(huán)境在interceptor里面已經(jīng)設(shè)置到request里了,還代理干啥?

往后看,我認(rèn)為關(guān)鍵在這里,

beanRegistry.setLoadBalancerInterceptor(new CompensableLoadBalancerInterceptor(this.statefully)

大家知道,feign通過ribbon組件進(jìn)行的復(fù)雜均衡,即chooseInstance,選擇請(qǐng)求往哪個(gè)實(shí)例上發(fā),如果還是輪訓(xùn)或隨機(jī),第一次try請(qǐng)求發(fā)到某實(shí)例,第二次confirm/cancel發(fā)到其他實(shí)例,別的實(shí)例上沒有try帶來(lái)的的事務(wù)信息,會(huì)非常不方便,也不知道try到底什么情況,

所以這里要多次請(qǐng)求粘滯到一個(gè)實(shí)例上。所以bytetcc實(shí)現(xiàn)了ribbon算法CompensableLoadBalancerRuleImpl,不支持自定義rule

(3)服務(wù)接收方接受,首先經(jīng)過CompensableHandlerInterceptor的preHandle,解析出事務(wù)上下文transactionContext,封裝成TransactionRequestImpl,并在response里加上一些header信息。這在發(fā)起者那里因?yàn)闆]有header的上下文,所以在(1)是什么都不做的

再到 CompensableMethodInterceptor, 解析方法。

再到 TransactionManager,即bytetcc的TransactionManagerImpl,到這里的begin,由于剛接到請(qǐng)求的時(shí)候,這時(shí)候已經(jīng)有事務(wù)了,所以調(diào)用的是一般的本地事務(wù)compensableManager.begin(),最后開啟一個(gè)本地事務(wù),然后執(zhí)行本地方法,執(zhí)行commit。

下圖可以簡(jiǎn)單介紹這個(gè)過程。

如何在springcloud中使用bytetcc實(shí)現(xiàn)數(shù)據(jù)的強(qiáng)一致性

5 try調(diào)用失敗,cancel過程

(1)如果有任意一個(gè)try失敗,那么要把已經(jīng)成功的try給回滾掉,spring通用的transactionInterceptor的處理過程,invokeWithinTransaction方法,如果有異常,catch住執(zhí)行

completeTransactionAfterThrowing(),然后到transactionManagerImpl的rollback,繼續(xù)到CompensableManager的collback

(2)CompensableTransanctionImpl中,fireRemoteParticipantCancel是真正的rollback,里面維護(hù)了一個(gè)resourcelist,按順序記錄了其他各個(gè)服務(wù)在try的時(shí)候調(diào)用的服務(wù),在這里循環(huán)這個(gè)list調(diào)用SpringCloudCoordinator,拼接cancel地址,帶著事務(wù)id發(fā)送請(qǐng)求過去。

(3)接收方,CompensableCoordinatorController的rollback,核心是從CompensableTransactionImpl到SpringContainerContextImpl 的 cancel,得到請(qǐng)求的controller的對(duì)應(yīng)的cancel方法,封裝到cancellableKey,然后拿到處理cancel的真實(shí)的bean,

Object instance = this.applicationContext.getBean(cancellableKey);
this.cancelComplicated(method, instance, args);

進(jìn)而執(zhí)行cancel對(duì)應(yīng)的bean的方法。整個(gè)過程可以如下概括

如何在springcloud中使用bytetcc實(shí)現(xiàn)數(shù)據(jù)的強(qiáng)一致性

6 try成功,confirm過程

同理,transactionManagerImpl的commit,最終到達(dá)CompensableTransactionImp進(jìn)行fireCommit,先提交本地事務(wù),然后fireRemoteParticipantConfirm,和cancel一模一樣,讀取resourceList,遍歷list發(fā)送請(qǐng)求到各個(gè)服務(wù)端。

各個(gè)服務(wù)方CompensableCoordinatorController的commit,拿到confirmablekey,找到confirm的bean進(jìn)行confirm。

如何在springcloud中使用bytetcc實(shí)現(xiàn)數(shù)據(jù)的強(qiáng)一致性

7 “compensable”的補(bǔ)償

(1)如果cancel,commit有失敗(失敗包含runtimeexception和自定義的一些異常),那么如何進(jìn)行補(bǔ)償,上面提到的一開始就啟動(dòng)的CompensableWork線程的run里面,其實(shí)有個(gè)while(true),每隔100秒循環(huán)一次,調(diào)用組件TransactionRecovery(看名字就知道恢復(fù)事務(wù)用的)的timingRecover,就是定時(shí)回復(fù),會(huì)調(diào)用到CompensableTransactionImpl的recoveryRollback/recoveryCommit,還是SpringCloudCoordinator發(fā)送的請(qǐng)求。

(2)如果出現(xiàn)宕機(jī),重啟后也是通過CompensableWork線程的run,第一步是init,嘗試恢復(fù)現(xiàn)有的事務(wù)。

a 如果try沒有執(zhí)行完就down機(jī),恢復(fù)時(shí)把已執(zhí)行的try給cancel掉。因?yàn)槭聞?wù)一般是業(yè)務(wù)請(qǐng)求觸發(fā)的,down機(jī)就請(qǐng)求失敗了,沒必要重啟后還恢復(fù)剛才的請(qǐng)求。
b 如果是confirm/cancel有沒成功的,會(huì)一直定時(shí)進(jìn)行confirm/cancel。

看完上述內(nèi)容,你們對(duì)如何在springcloud中使用bytetcc實(shí)現(xiàn)數(shù)據(jù)的強(qiáng)一致性有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI