springcloud結(jié)合bytetcc實(shí)現(xiàn)數(shù)據(jù)強(qiáng)一致性原理解析
1 使用背景和約束
公司使用的是springcloud,面臨分布式事務(wù)的場(chǎng)景的時(shí)候,可以使用對(duì)springcloud支持比較好的byte-tcc框架,git目前2600星,使用起來也非常方便,原理也很清晰,非常適合學(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í)解析出來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收集起來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代理,代替原來的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),原來的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帶來的的事務(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è)過程。
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è)過程可以如下概括
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。
7 “compensable”的補(bǔ)償
(1)如果cancel,commit有失?。ㄊ“瑀untimeexception和自定義的一些異常),那么如何進(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。
到此這篇關(guān)于springcloud結(jié)合bytetcc實(shí)現(xiàn)數(shù)據(jù)強(qiáng)一致性原理剖析的文章就介紹到這了,更多相關(guān)springcloud實(shí)現(xiàn)數(shù)據(jù)強(qiáng)一致性內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)最小生成樹MST的兩種解法
最小生成樹(MST)指在連通圖的所有生成樹中,所有邊的權(quán)值和最小的生成樹。本文介紹了求最小生成樹的兩種方法:Prim算法和Kruskal算法,需要的可以參考一下2022-05-05Java實(shí)現(xiàn)簡(jiǎn)單的斗地主游戲
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)簡(jiǎn)單的斗地主游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04java加密算法分享(rsa解密、對(duì)稱加密、md5加密)
這篇文章主要介紹了java加密算法,包括rsa解密、對(duì)稱加密、md5加密等,需要的朋友可以參考下2014-05-05解決Springboot集成Redis集群配置公網(wǎng)IP連接報(bào)私網(wǎng)IP連接失敗問題
在Springboot 集成 Redis集群配置公網(wǎng)IP連接報(bào)私網(wǎng)IP連接失敗,一直報(bào)私有IP連接失敗,所以本文小編給大家介紹了如何解決報(bào)錯(cuò)問題,如果有遇到相同問題的同學(xué),可以參考閱讀本文2023-10-10解決@PathVariable出現(xiàn)點(diǎn)號(hào).時(shí)導(dǎo)致路徑參數(shù)截?cái)喃@取不全的問題
這篇文章主要介紹了解決@PathVariable出現(xiàn)點(diǎn)號(hào).時(shí)導(dǎo)致路徑參數(shù)截?cái)喃@取不全的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08淺談java中的一維數(shù)組、二維數(shù)組、三維數(shù)組、多維數(shù)組
下面小編就為大家?guī)硪黄獪\談java中的一維數(shù)組、二維數(shù)組、三維數(shù)組、多維數(shù)組。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-05-05Intellij Idea插件開發(fā)之創(chuàng)建項(xiàng)目層級(jí)的右鍵菜單
這篇文章主要介紹了Intellij Idea插件開發(fā)之創(chuàng)建項(xiàng)目層級(jí)的右鍵菜單,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-02-02Java ES(Elasticsearch) 中的and 和 or 查
Elasticsearch 是一個(gè)分布式、高擴(kuò)展、高實(shí)時(shí)的搜索與數(shù)據(jù)分析引擎,es中match查詢中,查詢字符串分詞后,默認(rèn)是or或者的關(guān)系,這篇文章主要介紹了ES 中的and 和 or 查詢,需要的朋友可以參考下2022-11-11