當(dāng)前位置:首頁 > 公眾號(hào)精選 > 架構(gòu)師社區(qū)
[導(dǎo)讀]? ? ? 一、背景 有一定分布式開發(fā)經(jīng)驗(yàn)的朋友都知道,產(chǎn)品/項(xiàng)目/系統(tǒng)最初為了能夠快速迭代上線,往往不太注重產(chǎn)品/項(xiàng)目/系統(tǒng)的高可靠性、高性能與高擴(kuò)展性,采用單體應(yīng)用和單實(shí)例數(shù)據(jù)庫的架構(gòu)方式快速迭代開發(fā);當(dāng)產(chǎn)品/項(xiàng)目/系統(tǒng)做到一定規(guī)模的時(shí)候,原有的系

【分布式事務(wù)】tcc-transaction分布式TCC型事務(wù)框架搭建與實(shí)戰(zhàn)案例(基于Dubbo/Dubbox)

     

一、背景


有一定分布式開發(fā)經(jīng)驗(yàn)的朋友都知道,產(chǎn)品/項(xiàng)目/系統(tǒng)最初為了能夠快速迭代上線,往往不太注重產(chǎn)品/項(xiàng)目/系統(tǒng)的高可靠性、高性能與高擴(kuò)展性,采用單體應(yīng)用和單實(shí)例數(shù)據(jù)庫的架構(gòu)方式快速迭代開發(fā);當(dāng)產(chǎn)品/項(xiàng)目/系統(tǒng)做到一定規(guī)模的時(shí)候,原有的系統(tǒng)架構(gòu)則不足以支撐義務(wù)發(fā)展需要,往往相同的業(yè)務(wù)則需要重復(fù)寫很多次,導(dǎo)致代碼大量冗余,難以維護(hù)和擴(kuò)展,這時(shí)不得不對(duì)原有產(chǎn)品/項(xiàng)目/系統(tǒng)進(jìn)行拆分,引入分布式的系統(tǒng)架構(gòu);而對(duì)原有產(chǎn)品/項(xiàng)目/系統(tǒng)進(jìn)行拆分的過程中,對(duì)于業(yè)務(wù)和數(shù)據(jù)的拆分和遷移則成為了最為棘手的問題,尤其是在原有業(yè)務(wù)不能下線,拆分后的業(yè)務(wù)同時(shí)上線的場(chǎng)景下這種問題更加突出;項(xiàng)目拆分后,業(yè)務(wù)被拆分為多個(gè)獨(dú)立的子業(yè)務(wù)分散到多個(gè)子系統(tǒng)中,而原有的單一數(shù)據(jù)庫則被拆分到多個(gè)數(shù)據(jù)庫中,拆分后的數(shù)據(jù)庫則同樣又面臨著讓人頭疼的分布式事務(wù)的問題。


本文就針對(duì)項(xiàng)目拆分后數(shù)據(jù)庫的分布式事務(wù)問題,基于tcc-transaction分布式TCC型事務(wù)進(jìn)行框架的搭建,同時(shí)引入相關(guān)的實(shí)戰(zhàn)案例,來解決讓人頭疼的分布式事務(wù)問題。


二、tcc-transaction框架介紹


介紹:tcc-transaction是開源的TCC補(bǔ)償性分布式事務(wù)框架,Git地址:https://github.com/changmingxie/tcc-transaction

TCC為Try、Confirm、Cancel的縮寫:try階段預(yù)留資源嘗試提交,confirm階段確定提交,cancel取消提交釋放資源。

1.2.x項(xiàng)目指南地址:https://github.com/changmingxie/tcc-transaction/wiki/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%971.2.x

本文的例子為引入一個(gè)本人實(shí)際工作中的一個(gè)開發(fā)場(chǎng)景:創(chuàng)建資產(chǎn),將資產(chǎn)信息同時(shí)同步到Mongo與ES的流程(ES代碼不列出了,與mongo類似),整個(gè)流程保證數(shù)據(jù)一致


三、項(xiàng)目流程


1.下載1.2.x版本源碼,并可能需要修改部分代碼


因?yàn)槭堑谌桨?,所以需要自己打包到本地倉庫。但包中spring版本為3.2.12.RELEASE,如果本地項(xiàng)目為4.x,比如本人的項(xiàng)目spring版本為4.3.4.RELEASE,如果不修改tcc中的spring版本,將報(bào)錯(cuò)無法啟動(dòng),所以需要對(duì)原有框架源碼進(jìn)行相應(yīng)的修改。

源碼修改比較簡單,如下


1.1 修改tcc-transaction總pom.xml文件 


<!-- 第一處:修改版本為4.3.4 --><springframework.version>4.3.4.RELEASE</springframework.version> <!-- 第二處:修改版本為2.2.1 --><dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.2.1</version> <exclusions> <exclusion> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> </exclusion> </exclusions></dependency> <!-- 第三處:修改版本為2.5.3 --><dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>2.5.3</version></dependency>


1.2 修改 Java類

tcc-transaction-spring/src/main/java/org/mengyun/tcctransaction/spring/recover/RecoverScheduledJob.java

該文件中 CronTriggerBean類在4.x中已經(jīng)不存在,也是修改源碼主要修改的地方。

修改其中的init方法,修改后如下:


public void init() { try { MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean(); jobDetail.setTargetObject(transactionRecovery); jobDetail.setTargetMethod("startRecover"); jobDetail.setName("transactionRecoveryJob"); jobDetail.setConcurrent(false); jobDetail.afterPropertiesSet();  CronTriggerFactoryBean cronTrigger = new CronTriggerFactoryBean(); cronTrigger.setBeanName("transactionRecoveryCronTrigger"); cronTrigger.setJobDetail(jobDetail.getObject());  cronTrigger.setCronExpression(transactionConfigurator.getRecoverConfig().getCronExpression()); cronTrigger.afterPropertiesSet();  scheduler.scheduleJob(jobDetail.getObject(), cronTrigger.getObject());  scheduler.start();  } catch (Exception e) { throw new SystemException(e); }}


各位也可參考如下的修改:https://github.com/changmingxie/tcc-transaction/pull/84/files


1.3 打包并發(fā)布


這里我們通過Maven進(jìn)行打包發(fā)布,命令為:


mvn -Dmaven.test.skip=true install


2.項(xiàng)目依賴


參考1.2.x使用指南,引入兩個(gè)依賴(本人項(xiàng)目dubbo/dubbox框架,我使用并打包時(shí)版本為1.2.3.1)。調(diào)用方和提供方都需要引入依賴。


3.加載tcc-transaction.xml配置


原文中是配置在web.xml中,我個(gè)人試了一下放在dubbo web項(xiàng)目的web.xml中,但配置并沒有被加載。該文件的意義只是希望項(xiàng)目啟動(dòng)時(shí)被加載,于是直接在dubbo中的一個(gè)spring的配置文件中引入,如下:


<!-- TCC Transaction --><import resource="classpath:tcc-transaction.xml" />

該文件里面提供各種aop邏輯,項(xiàng)目啟動(dòng)時(shí)掃描指定注解,并做增強(qiáng)。


4.設(shè)置TransactionRepository‘


需要為tcc配置數(shù)據(jù)源,可以是MySQL或其他nosql,本文使用mysql,其他可參見原指南文檔。
mysql配置如下:

<!--tcc--><bean id="tccDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.tcc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="initialSize" value="${dbcp.initialSize}" /> <property name="maxActive" value="${dbcp.maxActive}" /> <property name="maxIdle" value="${dbcp.maxIdle}" /> <property name="maxWait" value="${dbcp.maxWait}" /> <property name="poolPreparedStatements" value="${dbcp.poolPreparedStatements}" /> <property name="defaultAutoCommit" value="${dbcp.defaultAutoCommit}" /> <property name="timeBetweenEvictionRunsMillis" value="${dbcp.timeBetweenEvictionRunsMillis}" /> <property name="minEvictableIdleTimeMillis" value="${dbcp.minEvictableIdleTimeMillis}" /></bean> <bean id="transactionRepository" class="org.mengyun.tcctransaction.spring.repository.SpringJdbcTransactionRepository"> <property name="dataSource" ref="tccDataSource"/> <property name="domain" value="SAAS"/> <property name="tbSuffix" value="_ASSET"/></bean> <bean class="org.mengyun.tcctransaction.spring.recover.DefaultRecoverConfig"> <property name="maxRetryCount" value="30"/> <property name="recoverDuration" value="120"/> <property name="cronExpression" value="0 */1 * * * ?"/> <property name="delayCancelExceptions"> <util:set> <value>com.alibaba.dubbo.remoting.TimeoutException</value> </util:set> </property></bean>


需要注意的點(diǎn):

1.數(shù)據(jù)源必須配置新的,不能使用之前項(xiàng)目存在的dataSource的bean,也不能在同一庫中,不然會(huì)導(dǎo)致tcc表數(shù)據(jù)與本地事務(wù)一起回滾,從而無法保存異常事務(wù)日志;

2.注意domain、tbSuffix的配置,這兩項(xiàng)文檔中并沒有配置,但源碼demo中配置了,用于數(shù)據(jù)庫的表名稱等,推薦配置;

3.最后的DefaultRecoverConfig項(xiàng)是可選的,用于恢復(fù)與重試,具體作用參考使用指南;

4.defaultAutoCommit必須為true(默認(rèn)為true)


5.mysql建表腳本


根據(jù)以上的tbSufifix配置,腳本如下:


CREATE TABLE `tcc_transaction_asset` ( `TRANSACTION_ID` int(11) NOT NULL AUTO_INCREMENT, `DOMAIN` varchar(100) DEFAULT NULL, `GLOBAL_TX_ID` varbinary(32) NOT NULL, `BRANCH_QUALIFIER` varbinary(32) NOT NULL, `CONTENT` varbinary(8000) DEFAULT NULL, `STATUS` int(11) DEFAULT NULL, `TRANSACTION_TYPE` int(11) DEFAULT NULL, `RETRIED_COUNT` int(11) DEFAULT NULL, `CREATE_TIME` datetime DEFAULT NULL, `LAST_UPDATE_TIME` datetime DEFAULT NULL, `VERSION` int(11) DEFAULT NULL, PRIMARY KEY (`TRANSACTION_ID`), UNIQUE KEY `UX_TX_BQ` (`GLOBAL_TX_ID`,`BRANCH_QUALIFIER`)) ENGINE=InnoDB DEFAULT CHARSET=utf8


如果表名稱不對(duì),啟動(dòng)過程會(huì)報(bào)錯(cuò),這時(shí),對(duì)數(shù)據(jù)表做相應(yīng)調(diào)整即可。


6.發(fā)布服務(wù)(重點(diǎn))


6.1 dubbo接口


 /** * @author binghe * 資產(chǎn)相關(guān)的業(yè)務(wù)發(fā)布Dubbo服務(wù) */public interface AssetCardService {  /** * 測(cè)試預(yù)保存資產(chǎn)(狀態(tài)為待確認(rèn)) */ @Compensable int testSaveAssetCard(AssetCardModel model);  /** * 確認(rèn)保存資產(chǎn)到mysql(狀態(tài)為正常) */ int confirmMysqlSaveAssetCard(AssetCardModel model);  /** * 取消保存資產(chǎn)到msyql(更新狀態(tài)為刪除) */ int cancelMysqlSaveAssetCard(AssetCardModel model);  /** * 預(yù)保存資產(chǎn)到mongo(狀態(tài)為待確認(rèn)) */ @Compensable void processMongo(AssetCardModel model);  /** * 確認(rèn)保存資產(chǎn)到mongo(狀態(tài)為正常) */ void confirmMongoSaveAssetCard(AssetCardModel model);  /** * 取消保存資產(chǎn)到mongo(更新狀態(tài)為刪除) */ void cancelMongoSaveAssetCard(AssetCardModel model); }


需要注意的點(diǎn):
1.對(duì)外提供服務(wù)的接口必須有@Compensable注解;
2.對(duì)應(yīng)的confirm與cancel方法必須聲明為接口,不能聲明為private,即使是public也不行,必須有接口。


6.2 dubbo接口實(shí)現(xiàn)類


/*** @author binghe* 資產(chǎn)相關(guān)的業(yè)務(wù)發(fā)布Dubbo服務(wù)的實(shí)現(xiàn)*/@Service@Componentpublic class AssetCardServiceImpl implements AssetCardService{ @Override @Compensable(confirmMethod = "confirmMysqlSaveAssetCard", cancelMethod = "cancelMysqlSaveAssetCard", transactionContextEditor = DubboTransactionContextEditor.class) @Transactional(propagation = Propagation.REQUIRED, rollbackFor = { Exception.class }) public int testSaveAssetCard(AssetCardModel model){  // 保存mysql,data狀態(tài)為-1 model.setDataStatus(-1); assetCardDao.insert(model);   // mongo處理 assetCardService.processMongo(model);  return model.getId(); }  @Override public int confirmMysqlSaveAssetCard(AssetCardModel model){ System.out.println("============================================================================"); System.out.println("=================mysql:confirm"); System.out.println("============================================================================");  // 更新mysql data_status為0 model.setDataStatus(0); assetCardDao.updateByPrimaryKey(model);  return model.getId(); }  @Override public int cancelMysqlSaveAssetCard(AssetCardModel model){ System.out.println("============================================================================"); System.out.println("=================mysql:cancel"); System.out.println("============================================================================");  // 更新mysql data_status為-1 model.setDataStatus(-1); assetCardDao.updateByPrimaryKey(model);  return model.getId(); }  @Compensable(confirmMethod = "confirmMongoSaveAssetCard", cancelMethod = "cancelMongoSaveAssetCard", transactionContextEditor = DubboTransactionContextEditor.class) @Override public void processMongo(AssetCardModel model) {  // 保存mongo,data_statu為-1 model.setDataStatus(-1); assetCardDaoWrapper.saveMongo(model); }  @Override public void confirmMongoSaveAssetCard(AssetCardModel model){ System.out.println("============================================================================"); System.out.println("=================mongo:confirm"); System.out.println("============================================================================");  // 更新mongo data_status為0 model.setDataStatus(0); assetCardDaoWrapper.updateMongo(model); }  @Override public void cancelMongoSaveAssetCard(AssetCardModel model){ System.out.println("============================================================================"); System.out.println("=================mongo:cancel"); System.out.println("============================================================================");  // 更新mongo data_status為-1 model.setDataStatus(-1); assetCardDao.updateByPrimaryKey(model); assetCardDaoWrapper.updateMongo(model); }}


注意點(diǎn):

1.對(duì)外提供服務(wù)的接口必須有@Compensable注解,同時(shí)必須有confirmMethod、cancelMethod參數(shù)的配置,同時(shí)dubbo接口額外增加 "transactionContextEditor = DubboTransactionContextEditor.class"這個(gè)配置;

2.提供服務(wù)接口與對(duì)應(yīng)另外的兩個(gè)CC方法參數(shù)必須完全一致;

3.該tcc框架可嵌套調(diào)用,如上在testSaveAssetCard方法,即try階段中調(diào)用了另一個(gè)tcc方法"assetCardService.processMongo()",理論上嵌套只應(yīng)該在try階段進(jìn)行;

4.confirm、cancel需要實(shí)現(xiàn)冪等性,可能會(huì)被重試;5.由于網(wǎng)絡(luò)等因素,可能導(dǎo)致cancel方法先執(zhí)行,cancel方法一定要做好相應(yīng)的判斷與處理


6.3 調(diào)用方

@Override@Transactional(propagation = Propagation.REQUIRED, rollbackFor = { Exception.class })public long testSaveAssetCard(AssetCardModel assetCardModel) throws AssetException { assetCardModel.setId(IdGenerator.getId());  return assetCardService.testSaveAssetCard(assetCardModel);}


注意點(diǎn):

1.因?yàn)樾枰貪L更新等操作,所以此業(yè)務(wù)中id不能用自增,而是需要項(xiàng)目生成;

2.特別注意,調(diào)用方必須在事務(wù)中,也就是說必須有事務(wù)注解,或者能被事務(wù)配置切到,沒有事務(wù)tcc框架調(diào)用時(shí)會(huì)拋異常。

至此,配置已經(jīng)全部完成。


7.事務(wù)查看


源碼中提供tcc-transaction-server web項(xiàng)目,該項(xiàng)目提供界面查看事務(wù)日志,打包后部署即可,我們這里就不在作詳細(xì)的描述。


四、TCC執(zhí)行流程


業(yè)務(wù)流程使用記錄:

前提:用戶下單,建立訂單,創(chuàng)建支付記錄,支付記錄狀態(tài)為待支付

try:

用戶金額凍結(jié)

調(diào)用積分處理TCC:

try:預(yù)增加積分

confirm:更新增加積分狀態(tài)

cancel:取消增加的積分

confirm:

訂單支付狀態(tài)更新為已支付

訂單支付記錄支付狀態(tài)更新為已支付

用戶金額扣款(以上三個(gè)操作在同一本地事務(wù))

cancel:

判斷訂單支付狀態(tài)與訂單記錄支付狀態(tài)為未支付

用戶凍結(jié)金額釋放


特別推薦一個(gè)分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒關(guān)注的小伙伴,可以長按關(guān)注一下:

【分布式事務(wù)】tcc-transaction分布式TCC型事務(wù)框架搭建與實(shí)戰(zhàn)案例(基于Dubbo/Dubbox)

長按訂閱更多精彩▼

【分布式事務(wù)】tcc-transaction分布式TCC型事務(wù)框架搭建與實(shí)戰(zhàn)案例(基于Dubbo/Dubbox)

如有收獲,點(diǎn)個(gè)在看,誠摯感謝

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問題,請(qǐng)聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡稱"軟通動(dòng)力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉