摘要:最終一致性通常使用消息機(jī)制來設(shè)計,其核心是消息的安全送達(dá)與消費(fèi)。事務(wù)狀態(tài),在的不通階段進(jìn)行事務(wù)狀態(tài)的變更。除了,最近項目中還涉及了安全消息,等弄清楚了再來一發(fā)。
TCC 開源項目源碼學(xué)習(xí)(一)
學(xué)習(xí)TCC分布式事務(wù)的知識是使用了GIT上的一個開源項目,之前有簡單的看過一些,有了一個大概的了解,但是隨著時間的‘清洗’,又開始變得‘渾濁不清’了,這次索性把這份源碼從頭看了下,又把流程推演了好幾次,覺得已經(jīng)懂了,想把理解的東西通過博客寫出來。在這個過程中,再次梳理一遍,加深理解,同時也可以發(fā)現(xiàn)一些理解偏差和錯誤的地方。
GIT: https://github.com/1755728288...
在分布式系統(tǒng)中,為了保證執(zhí)行指令的正確性,引入了事務(wù)的概念。一般意義上,事務(wù)主要突出的是ACID,即原子性,一致性,隔離性和持久性。而在分布式事務(wù)中,最主要的原子性的保證,要保證一個業(yè)務(wù)操作在其分布式系統(tǒng)中的所有指令全部執(zhí)行或不執(zhí)行。因為各個指令的操作耗時以及順序,所以原子性都對應(yīng)了一定的時間窗口,比如單機(jī)系統(tǒng)下,這個時間窗口非常短,而且其原子性也由數(shù)據(jù)庫事務(wù)來保證。而在分布式系統(tǒng)中,原子性就依賴于具體的應(yīng)用設(shè)計了,主要的依據(jù)是業(yè)務(wù)上允許的時間窗口的長短。目前一般來說,若允許的時間窗口較短,就用TCC,若允許的時間窗口較長,則使用最終一致性。最終一致性通常使用消息機(jī)制來設(shè)計,其核心是消息的安全送達(dá)與消費(fèi)。
TCC可以說是一種設(shè)計模式,將傳統(tǒng)單機(jī)系統(tǒng)中的大事務(wù)進(jìn)行拆分,通過小事務(wù)來拼裝,并協(xié)調(diào)整體的事務(wù)提交和回滾。按字面意思理解,TCC主要分Try,Confirm,Cancel三個階段,Try預(yù)留資源,Confirm使用預(yù)留資源執(zhí)行業(yè)務(wù)操作,Cancel則是釋放資源。貌似簡單,但是在實際的業(yè)務(wù)場景中,往往會困惑我們在這三個階段分別要做什么,這個是業(yè)務(wù)設(shè)計的一部分內(nèi)容了,在此不展開敘述。不過要注意的一點時,任何一個階段的結(jié)果都是可見的,比如一個庫存子服務(wù)的入庫方法,在try階段就直接加到庫存上去了,回滾的時候發(fā)現(xiàn)剛加上的庫存已經(jīng)有買家下單準(zhǔn)備出庫了,那就GG了。
代碼結(jié)構(gòu)先放個圖,這是項目的組件,上面綠色區(qū)域是框架的子模塊,下面黃色的是樣例模塊。不要被黃色區(qū)域嚇到,TCC的代碼不多,主要是tcc-transaction-core.
組件名 | 說明 |
---|---|
tcc-transaction-core | 【重要】核心代碼,主要的切面和job |
tcc-transaction-api | 注解和常量 |
tcc-transaction-spring | spring使用的擴(kuò)展 |
tcc-transaction-dubbo | dubbo使用時的擴(kuò)展 |
tcc-transaction-server | tcc事務(wù)管理控制臺 |
tcc-transaction-unit-test | 單元測試 |
tcc-transaction-tutorial-sample | 訂單場景示例工程,分dubbo和spring版本 |
所以重點只有一個模塊,就是tcc-transaction-core,代碼量不多,主要分成下面的模塊
模塊名 | 說明 |
---|---|
interceptor | 【重要】2個切面,一個是根據(jù)事務(wù)狀態(tài)進(jìn)行tcc階段方法的選擇,一個是組織事務(wù)的參與者,用XID將各個參與者綁起來,以便事務(wù)的提交和回滾 |
recover | 恢復(fù)在同步調(diào)用失敗事務(wù)的定時處理 |
repository | 事務(wù)對象持久DAO對象,提供了多種選擇:緩存,文件系統(tǒng),jdbc,zookeeper |
context | 傳遞事務(wù)上下文的方法類,一般會在遠(yuǎn)程方法調(diào)用的切面中,將上下文加入到參數(shù)列表中 |
common | 方法枚舉和事務(wù)枚舉 |
serializer | 事務(wù)對象的序列化器,使用了kyto序列化工具 |
support | 實現(xiàn)了工廠類,管理TCC的類的實例化 |
utils | 工具類 |
最重要的是interceptor,是主要業(yè)務(wù)邏輯所在。
題外話,有人初看了一遍TCC,拿來和數(shù)據(jù)庫事務(wù)來比較,會說‘TCC不就是業(yè)務(wù)補(bǔ)償么,沒什么難點啊’,貌似有道理,其實不然。還記得數(shù)據(jù)庫事務(wù)的ACID嗎?因為數(shù)據(jù)庫提供了事務(wù)特性,ACID由數(shù)據(jù)庫保證,應(yīng)用只需要一個簡單的@Transactional注解就都搞定了。而分布式事務(wù),就把ACID的特性從數(shù)據(jù)庫里面拿了出來,由應(yīng)用程序來保證。當(dāng)然ACID也有了和之前不一樣的含義。
特性 | 數(shù)據(jù)庫 | 分布式事務(wù) |
---|---|---|
A[Atomicity] | 所有數(shù)據(jù)庫更新一起提交和回滾,數(shù)據(jù)庫通過日志來實現(xiàn) | 通過協(xié)調(diào)各個事務(wù)的參與方,通過框架來處理異常 |
C[Consistncy] | 通過數(shù)據(jù)庫約束來實現(xiàn),比如非空,唯一,外鍵等 | |
I[Isolation] | 各個數(shù)據(jù)庫實現(xiàn)方式不一樣,主要分讀已提交,讀未提交等 | TCC通過try階段鎖定資源來實現(xiàn)隔離,即業(yè)務(wù)資源的隔離 |
D[Durability] | 數(shù)據(jù)庫實現(xiàn) | 數(shù)據(jù)庫實現(xiàn) |
以項目中的例子來說明整個TCC的處理流程,這個例子是類似電商下單的場景,在支付的時候可以選擇紅包或本金,比如一個筆記本4000,可以選擇使用3000的本金和1000的紅包來支付,有三個服務(wù):訂單服務(wù)(order),本金服務(wù)(capital)和紅包服務(wù)(redPacket)。主調(diào)方是Order,被調(diào)方Capital和RedPacket.
下面是絞盡腦汁畫的調(diào)用流程圖,為了圖的簡便直觀,省去了cancel的處理,只有try和confirm的調(diào)用(cancel的處理和confirm基本一致)。紅線是try的調(diào)用,藍(lán)線是confirm的調(diào)用。粗紅線是開始,粗藍(lán)線是結(jié)束。
在try階段的makePayment,方法調(diào)用了兩個子事務(wù)的api方法,其他的方法的調(diào)用均是全局事務(wù)切面決定的。業(yè)務(wù)方法只要按TCC框架的要求實現(xiàn)即可,其他的交給TCC框架。
我們從TCC事務(wù)注解開始,先介紹幾個基本的類。
/** 屬性主要是事務(wù)傳播屬性,提交方法,回滾方法,上下文傳遞類,是否異步提交,是否異步回滾 propagation屬性比較重要,值有required,support,mandatory和requireNEW,具體的含義和spring的事務(wù)傳播類似。 **/ public @interface Compensable { public Propagation propagation() default Propagation.REQUIRED; public String confirmMethod() default ""; public String cancelMethod() default ""; public Class extends TransactionContextEditor> transactionContextEditor() default DefaultTransactionContextEditor.class; public boolean asyncConfirm() default false; public boolean asyncCancel() default false;
/** 事務(wù)狀態(tài),在TCC的不通階段進(jìn)行事務(wù)狀態(tài)的變更。 **/ public enum TransactionStatus { TRYING(1), CONFIRMING(2), CANCELLING(3);
/** ROOT:事務(wù)啟動方法,比如例子中order的makePayment方法 PROVIDER:事務(wù)協(xié)同方法,比如例子中redpacket的record方法 Normal:普通方法,比如API里面的record方法 **/ public enum MethodType { ROOT, PROVIDER, NORMAL; }
/** ROOT:主事務(wù),一般MethodType為ROOT的啟動 BRANCH:分支事務(wù) **/ public enum TransactionType { ROOT(1), BRANCH(2);
下面我們看一下事務(wù)持久的內(nèi)容,能更好的幫助理解。每個服務(wù)都會有一張事務(wù)表,會記錄所有的ROOT和BRANCH事務(wù),在事務(wù)完成之后,會自動刪除。
TRANSACTION_ID | DOMAIN | GLOBAL_TX_ID | BRANCH_QUALIFIER | CONTENT | STATUS | TRANSACTION_TYPE | RETRIED_COUNT | VERSION |
---|---|---|---|---|---|---|---|---|
2 | ORDER | 事務(wù)編號001 | 分支標(biāo)識A | 事務(wù)對象A | 2 | 1 | 0 | 11 |
2 | CAPITAL | 事務(wù)編號001 | 分支標(biāo)識B | 事務(wù)對象B | 2 | 2 | 0 | 11 |
2 | REDPACKET | 事務(wù)編號001 | 分支標(biāo)識C | 事務(wù)對象C | 2 | 2 | 0 | 11 |
domain區(qū)分了服務(wù)
global_tx_id 串起了所有的事務(wù)
transaction_type 1為主事務(wù),2為分支事務(wù)
content 用于恢復(fù)事務(wù),主事務(wù)的content中包含了參與者
代碼部分的最后是兩個切面和恢復(fù)的job,不進(jìn)行特別的解釋了,將我的理解放在代碼的注釋里面。
//CompensableTransactionInterceptor.java public Object interceptCompensableMethod(ProceedingJoinPoint pjp) throws Throwable { //獲取所執(zhí)行事務(wù)方法的設(shè)置屬性 Method method = CompensableMethodUtils.getCompensableMethod(pjp); Compensable compensable = method.getAnnotation(Compensable.class); Propagation propagation = compensable.propagation(); TransactionContext transactionContext = FactoryBuilder.factoryOf(compensable.transactionContextEditor()).getInstance().get(pjp.getTarget(), method, pjp.getArgs()); boolean asyncConfirm = compensable.asyncConfirm(); boolean asyncCancel = compensable.asyncCancel(); //當(dāng)前是否存在事務(wù), boolean isTransactionActive = transactionManager.isTransactionActive(); /** * 判斷方法類型 * MethodType一共有三種,Root,Provider,Normal. 主要通過propagation和isTransactionActive來確定 */ MethodType methodType = CompensableMethodUtils.calculateMethodType(propagation, isTransactionActive, transactionContext); switch (methodType) { case ROOT: //ROOT:(1)propagation為require_new (2)propataion為require,且之前沒有有事務(wù)存在 //主事務(wù)處理方法 return rootMethodProceed(pjp, asyncConfirm, asyncCancel); case PROVIDER: //PROVIDER:(1) propation為require或者mandatory,且之前有事務(wù) //從事務(wù)處理方法 return providerMethodProceed(pjp, transactionContext, asyncConfirm, asyncCancel); default: //普通方法執(zhí)行 return pjp.proceed(); } } private Object rootMethodProceed(ProceedingJoinPoint pjp, boolean asyncConfirm, boolean asyncCancel) throws Throwable { Object returnValue = null; Transaction transaction = null; try { //開啟新的主事務(wù),使用新生成的xid,狀態(tài)為trying,事務(wù)類型為ROOT transaction = transactionManager.begin(); try { returnValue = pjp.proceed(); } catch (Throwable tryingException) { if (!isDelayCancelException(tryingException)) { logger.warn(String.format("compensable transaction trying failed. transaction content:%s", JSON.toJSONString(transaction)), tryingException); //異常回滾,將事務(wù)狀態(tài)更新為CANCELLING transactionManager.rollback(asyncCancel); } throw tryingException; } //執(zhí)行confirm方法,將事務(wù)狀態(tài)更新為CONFIRMING。如果是異步,則TM會使用線程池異步執(zhí)行,否則直接調(diào)用,會協(xié)調(diào)所有的參與者進(jìn)行提交。并將事務(wù)記錄刪除 transactionManager.commit(asyncConfirm); } finally { //清理事務(wù)數(shù)據(jù) transactionManager.cleanAfterCompletion(transaction); } return returnValue; } private Object providerMethodProceed(ProceedingJoinPoint pjp, TransactionContext transactionContext, boolean asyncConfirm, boolean asyncCancel) throws Throwable { Transaction transaction = null; try { switch (TransactionStatus.valueOf(transactionContext.getStatus())) { case TRYING: //開啟一個子事務(wù),并調(diào)用TCC的try方法 transaction = transactionManager.propagationNewBegin(transactionContext); return pjp.proceed(); case CONFIRMING: //獲取子事務(wù)TRY階段的事務(wù),并調(diào)用TCC的commit方法 try { transaction = transactionManager.propagationExistBegin(transactionContext); transactionManager.commit(asyncConfirm); } catch (NoExistedTransactionException excepton) { //the transaction has been commit,ignore it. } break; case CANCELLING: //獲取子事務(wù)保存的事務(wù)數(shù)據(jù),執(zhí)行cancel方法 try { transaction = transactionManager.propagationExistBegin(transactionContext); transactionManager.rollback(asyncCancel); } catch (NoExistedTransactionException exception) { //the transaction has been rollback,ignore it. } break; } } finally { //清理事務(wù)數(shù)據(jù) transactionManager.cleanAfterCompletion(transaction); } Method method = ((MethodSignature) (pjp.getSignature())).getMethod(); return ReflectionUtils.getNullValue(method.getReturnType()); } private boolean isDelayCancelException(Throwable throwable) { if (delayCancelExceptions != null) { for (Class delayCancelException : delayCancelExceptions) { Throwable rootCause = ExceptionUtils.getRootCause(throwable); if (delayCancelException.isAssignableFrom(throwable.getClass()) || (rootCause != null && delayCancelException.isAssignableFrom(rootCause.getClass()))) { return true; } } } return false; }總結(jié)
代碼分析的比較少,這份代碼還是有很多值得稱道的地方,工廠類,緩存,模板方法等設(shè)計模式的使用,下次可以從設(shè)計模式的角度來進(jìn)行分析。除了TCC,最近項目中還涉及了安全消息,等弄清楚了再來一發(fā)。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/74067.html
摘要:如上圖所示,的實際上是已中間件的形式放在應(yīng)用層,不用依賴數(shù)據(jù)庫對協(xié)議的支持,完全剝離了分布式事務(wù)方案對數(shù)據(jù)庫在協(xié)議支持上的要求。 微信公眾號「后端進(jìn)階」,專注后端技術(shù)分享:Java、Golang、WEB框架、分布式中間件、服務(wù)治理等等。 在微服務(wù)架構(gòu)體系下,我們可以按照業(yè)務(wù)模塊分層設(shè)計,單獨部署,減輕了服務(wù)部署壓力,也解耦了業(yè)務(wù)的耦合,避免了應(yīng)用逐漸變成一個龐然怪物,從而可以輕松擴(kuò)展,...
摘要:即服務(wù)不能無響應(yīng),或出錯分區(qū)的容忍性,這里的分區(qū)不是指數(shù)據(jù)分布式存儲中的分區(qū)。假設(shè)一個分布式系統(tǒng)中,有兩個節(jié)點,處于分區(qū)狀態(tài)。在大多數(shù)的分布式系統(tǒng)設(shè)計中,人們多會選擇滿足兩點特性。為了解決最終的一致性,這就涉及到分布式事務(wù)。 showImg(https://segmentfault.com/img/bV7kd4?w=500&h=253); 一、分布式的兩大場景 數(shù)據(jù)存儲的分布式 服務(wù)的...
摘要:即服務(wù)不能無響應(yīng),或出錯分區(qū)的容忍性,這里的分區(qū)不是指數(shù)據(jù)分布式存儲中的分區(qū)。假設(shè)一個分布式系統(tǒng)中,有兩個節(jié)點,處于分區(qū)狀態(tài)。在大多數(shù)的分布式系統(tǒng)設(shè)計中,人們多會選擇滿足兩點特性。為了解決最終的一致性,這就涉及到分布式事務(wù)。 showImg(https://segmentfault.com/img/bV7kd4?w=500&h=253); 一、分布式的兩大場景 數(shù)據(jù)存儲的分布式 服務(wù)的...
摘要:中大致分為兩部分事務(wù)管理器和本地資源管理器。具體實現(xiàn)分布式事務(wù)框架的核心功能是對本地事務(wù)的協(xié)調(diào)控制,框架本身并不創(chuàng)建事務(wù),只是對本地事務(wù)做協(xié)調(diào)控制。 Spring Cloud 分布式事務(wù)管理 在微服務(wù)如火如荼的情況下,越來越多的項目開始嘗試改造成微服務(wù)架構(gòu),微服務(wù)即帶來了項目開發(fā)的方便性,又提高了運(yùn)維難度以及網(wǎng)絡(luò)不可靠的概率. @[toc]在說微服務(wù)的優(yōu)缺點時,有對比才會更加明顯,首先...
閱讀 1646·2023-04-25 20:36
閱讀 2065·2021-09-02 15:11
閱讀 1206·2021-08-27 13:13
閱讀 2659·2019-08-30 15:52
閱讀 4689·2019-08-29 17:13
閱讀 1008·2019-08-29 11:09
閱讀 1498·2019-08-26 11:51
閱讀 843·2019-08-26 10:56