電影售票系統開發流程及其bug修復日誌--高可用(2)

分佈式事務

首先,事務是用來保證一組數據操作的完整性和一致性。事務的四種特性,Atomicity原子性,consistency一致性,isolation隔離性,durability持久性。
分佈式事務大體可以分成兩部分,首先是事務,以前的分佈式是一個單體性的事務,其次就是分佈式,分佈式事務就是將多個節點的事務看成是一個整體。現在有十個節點,每臺機器部署了不同的應用,有訂單支付影片等等都部署在不同機器,如果在同一個事務裏,處理很簡單,但是如果是在不同的事務,不同機器上,其他的事務怎麼知道其他事務發生失敗了,失敗了又怎麼處理。分佈式事務一般由事務參與者,資源服務器,事務管理器組成。事務參與者類比於機器和服務,資源服務器就是用來控制比如庫存數量,金額等等,最後是事務管理器,是用來輔助,比如剛剛的例子,一個事務出事了,其他的事務怎麼知道,那麼這個事務管理就會通知其他事務。最常見的分佈式事務就是支付,下訂單等。


分佈式實現一般有兩種,兩段式事務和三段式事務,基於XA的分佈式事務,基於消息的最終一致性方案,這裏的信息一般是指信息隊列或者是Redis一致性緩存,還有TCC編程式補償性事務。

兩段式事務和三段式事務

TC就是事務管理器,先準備好數據交流,然後雙方開始提交,提交完成後告訴事務管理器,現在有兩個,如果只有一個,那麼久提交失敗,回退即可。但是這種兩段式還是有點問題的,都是在事務管理器要求你幹什麼就幹什麼,比如準備就緒了,事務管理器要你提交,結果有一臺機器出問題,你怎麼知道這個問題是在提交前還是提交後出現的,提交後出現的那就不用回滾了,提交前的那就要回滾。基於上述缺點,出現了三段式事務,但是都是基於兩段式,只不過把第一段分成了兩部分,第三段和兩段式的第二段一樣。三段式的第一階段是canCommit階段,事務管理器會給所有事務參與者發送canCommit,各個事務管理者根據自己的狀態查看能否提交,如果可以則回執OK,否者返回fail,並不開啓本地事務並執行。如果所有的都正常,則進入第二階段,否則停止;第二階段即是preCommit,事務協調器向所有參與者發起準備事務請求,參與者接受到後,開啓本地事務並執行,但是不提交。第三階段則是提交了。

基於XA的分佈式事務


本質上講還是一個兩段式的提交。這個流程和前面的二三段其實是差不多的,首先詢問準備好沒有,準備好回個OK,然後提交執行。流程差不多,但是調用方式出現了變化,但是不常見,應用場景多,MySQL,DB2這些關係型數據庫絕大多數都是基於XA來的。

基於消息一致性方案的分佈式事務


這裏和前面介紹的幾種方式有所不同,參與者有兩個A系統和消息中間件兩個,首先系統A發送預備消息,中間件保存預備消息後返回說我已經收到了。接着執行本地事務,執行後把執行結果告訴消息中間件,不一定是成功的,可能成功也可能失敗,消息中間件保存消息回調。可能會覺得回調和保存消息很多餘,我看到這個圖也是這麼想的,因爲執行事務無非就是成功和失敗,爲什麼要回調保存?但是別忘了,分佈式事務是多個事務之間的關係,我這裏只是一個事務,將多個事務結合在一起:

如果有一個B系統,那麼當A系統執行完成了,消息中間件通知了B:系統A執行完成了,然後B再執行。在整個售票系統中有一個支付系統,錢到賬了修改訂單狀態,但是如果錢到賬了修改訂單狀態失敗了怎麼辦?這個時候就可以使用消息一致性了,可以在支付寶下訂單支付的時候暫停線程,啓動另外一個線程進行修改訂單操作。修改成功了才啓用支付寶線程。這種方案是強一致性方案,同一個時刻成功一定成功,失敗一起失敗,不會存在其他情況,但是缺點也很明顯,會存在等待時期,會使得支付寶線程等待,這樣會影響性能。

TCC補償性事務


主要進行的三個操作依次是Try接口,Confirm接口,cancel接口,confirm接口和cancel接口只能使用一個,事務要麼成功要麼失敗。首先啓動事務協調器,告訴事務協調器要開始工作了,接着調用不同的服務,嘗試進行操作,比如扣減庫存和創建訂單,結果A成功了,B失敗了,那麼業務就會調用cancel接口,成功了調用從confirm接口。try,confirm,cancel接口都是在服務裏面實現,業務只是去調用這些接口,關心返回結果,根據返回結果確定是調用confirm還是cancel接口。cancel接口把try做過的東西全部取消,confirm確認提交,所以也稱爲是補償式。
基於消息一致性的事務是一種強一致性的事務,很大程度上會造成資源的浪費,尤其是對於時間的浪費,上面的例子是兩個事務,如果發展到多個事務,等待的時間就會更多了。但是他的優點也很明顯,就是強一致性,缺點也是強一致性。在實際工程中經常會有對接京東支付,阿里支付等等的場景,假設使用TCC,那麼問題來了,錢打進去是回不來的,想要調用cancel接口那隻能自己掏腰包,而消息一致性就沒有這種問題。TCC補償性事務是柔性事務,在try階段要對資源做預留,在確認和取消階段釋放資源,confirm沒有什麼,cancel做反向操作。相比基於消息一致性來說TCC的時效性更強。

主流的分佈式框架

全局事務服務,GTS
螞蟻金服分佈式事務,DTX
開源TCC框架,TCC-Transaction
開源TCC框架,ByteTCC
這裏使用TCC-Transaction開源框架。

api就是接口,core爲核心包,類似於guns-core的核心,server是事務監控工具,有多少事務,事務狀態,spring即是spring的支持,然後dubbo的支持,unit即爲測試,tutorial教程。簡要測試一下,打開簡要教程,dbscripts裏面執行SQL語句,會生成四個數據庫:

第一個tcc的庫是必須要建立的,只要使用就需要建立;下面的cap,ord,red分佈模擬了業務場景,cap爲資金賬戶,ord訂單,red紅包。tutorial裏面有兩個例子,一個是HTTP的例子,一個就是整合了dubbo的例子,HTTP的例子沒有什麼問題,很簡單。看看dubbo的例子,首先先要部署Tomcat:

order模塊的前綴/即可。打開web.xml,發現三個模塊的web.xml都沒有報錯,是由於執行順序的問題,錯誤提示**{The content of element type “web-app” must match "(icon?,display-name?,description?,distributable?,context-param,filter,filter-mapping*,listener*,servlet*,servlet-mapping*,session-config?,mime-mapping*,welcome-file-list?,error-page*,taglib*,resource-env-ref*,resource-ref*,security-constraint*,login-config?,security-role*,env-entry*,ejb-ref*,ejb-local-ref*)".- No grammar constraints (DTD or XML schema) detected for the document**}**,listener要再filter-mappering和servlet之間,調整位置就好了,啓動跑起來環境就搭建好了。現在就是要按照案例裏面的代碼把自己的項目改造一下。首先查看一下工程結構:

api和之前業務裏面的api接口沒有什麼區別,注意有一個接口:

帶有compensable標籤,即是需要進行事務處理的接口,RedPacketAccountServiceImpl這個接口實現是查詢紅包信息而已,不需要事務支持。

當前註解的方法就是try方法,就是業務方法,註解提到的confirmMethod,cancelMethod就是TCC,最後一個參數是事務上下文支持,這裏使用的是隱式上下文的支持,可能這裏需要用到隱式參數傳遞,接下來下面就要實現兩個confirm和cancel兩個方法,而且必須在同一個類裏,因爲註解絕大多數是要通過放射和AOP來讀取,如果不在同一個類裏面,是沒有辦法找到的,類是通過包名加上類名找到的,只返回一個字符串就能找到是不可能的,所以只有放在同一個類裏面才能找到

record也就是try業務,注意catch裏面不是捕獲所有的異常,業務TCC是根據異常的有無來判斷業務執行成功或者失敗,有異常纔會回滾,對於業務返回的結果不關心。這裏的訂單多出兩種狀態,draft草稿和cancel取消,draft即是try完但是沒有confirm的訂單,confirm完成就是真正支付完成的訂單了。

注意在cancel和confirm裏面都需要判斷訂單是空而且訂單狀態爲draft草稿狀態。**但是這裏涉及到一個服務冪等性的問題,即是一個服務重複多次執行和一次執行的結果相同,冪等性還不是理解的很好,做完這個服務再去看看。**所以TCC的分佈式事務需要注意兩個部分,1、分佈式事務裏,不要輕易在業務層捕獲所有異常,2、使用TCC-Transaction時,confirm和cancel的冪等性需要自己代碼支持。然後部署Tomcat,運行即可,注意TCC-trancation-order這個模塊,在部署artificial的時候路徑一個斜槓就好了。
測試完成之後就可以仿造加到這個項目上了。首先是打包,idea裏面maven的package功能打包:

控制檯用mvn install進去,當然了,最簡單的還是直接接idea裏maven install進去就好了。需要把剛剛的幾個tcc-transaction打包成jar加進原來的工程裏面才能使用tcc事務。
(2020.1.29.凌晨1:20)

部署環境

部署環境有點麻煩,比springboot麻煩多了,再加上文檔東寫一點西寫一點的,調試的頭都大了。install進去之後設置自己的tcc-transaction版本,我的設置是1.2.11版本,引入包之後出現了些問題:

這個問題之前也遇到過,一般就幾種情況,jdk版本不一致(這個可能性最高);pom包之間互相引用,但是調用的版本不一致;環境不一致;log4j的包沒有導入;之前的原因是因爲Tomcat使用jdk1.7,可是項目環境1.8,導致的問題,這裏gteLogger方法上網百度了一下,com.alibaba.dubbo.common這個包一直以來的重大更新都沒有去掉這個方法,LoggerAdapter這個類下一個版本將會淘汰,但是現在還有,不應該出現問題。那可能就是我打包的tcc-transaction的jar有問題了,翻了一下tcc-transaction的幾個項目,com.alibaba.dubbo這個以來全都依賴到,但是版本也都一致的,而我本來項目環境也沒有用到這個包,是直接引入alibobo-springboot的dubbo包的,很可能就是springboot的dubbo包的衝突導致。上網看了一下好像沒有找到有出過這樣的問題,但是有出現成功的例子,他的版本是1.2.4.23,拿來試了一下,結果就好了。應該是整合了nutz所導致的。在package tcctransaction的時候可能有些錯誤,需要在server目錄下面建立config/dev目錄,爲什麼要建立我也不知道,不創建他就提示錯誤,找不到目錄。
接下來就是按照部署文檔把項目部署上去,首先是把tcc-transaction-dubbo和tcc-transaction-spring目錄下兩個xml的文件拷貝過來。還要讀到springboot容器裏面,而主要要進行分佈式事務的就是支付模塊了,所以在guns-order加上一個config類,用於讀取配置文件:

啓動項目,出現錯誤
就說明是讀取成功了,還需要配置一個數據源的支持,transaction repository。按照文檔把bean拿過來:

<bean id="transactionRepository"
      class="org.mengyun.tcctransaction.spring.repository.SpringJdbcTransactionRepository">
    <property name="dataSource" ref="dataSource"/>
</bean>

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
      destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://127.0.0.1:3306/test"/>
    <property name="username" value="root"/>
    <property name="password" value=""/>
</bean>

BasicDataSource改成自己的數據源Alibaba那個,想用其他的也可以。dataSource需要單獨配置,不能和業務裏使用的dataSource複用,即使使用的是同一個數據庫。JOB恢復也要配置上,還有一些表空間等等,配置完成之後還需要創建表,tcc-transaction需要使用自己的一張表來存儲數據,需要自己創建。

tbSuffix後綴,這個模塊是訂單模塊,那麼後綴就是order,創建的表就是tcc_transaction_order,還需要加上數據源配置:

還有一些零零散散的配置加上即可,啓動一下沒有問題即可。這是服務提供者的配置,消費者的配置也是一樣的,gateway寫上相同的配置即可。


在gateway啓用相同配置並且啓動的時候,問題來了,在啓動的時候出現了問題:

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/zhanggong004/.m2/repository/org/slf4j/slf4j-log4j12/1.7.25/slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/C:/Users/zhanggong004/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
Exception in thread "main" java.lang.IllegalArgumentException: LoggerFactory is not a Logback LoggerContext but Logback is on the classpath. Either remove Logback or the competing implementation (class org.slf4j.impl.Log4jLoggerFactory loaded from file:/C:/Users/zhanggong004/.m2/repository/org/slf4j/slf4j-log4j12/1.7.25/slf4j-log4j12-1.7.25.jar). If you are using WebLogic you will need to add 'org.slf4j' to prefer-application-packages in WEB-INF/weblogic.xml: org.slf4j.impl.Log4jLoggerFactory
	at org.springframework.util.Assert.instanceCheckFailed(Assert.java:655)
	at org.springframework.util.Assert.isInstanceOf(Assert.java:555)
	at org.springframework.boot.logging.logback.LogbackLoggingSystem.getLoggerContext(LogbackLoggingSystem.java:286)
	at org.springframework.boot.logging.logback.LogbackLoggingSystem.beforeInitialize(LogbackLoggingSystem.java:102)
	at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationStartingEvent(LoggingApplicationListener.java:220)
	at org.springframework.boot.context.logging.LoggingApplicationListener.onApplicationEvent(LoggingApplicationListener.java:199)
	at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
	at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
	at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
	at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:127)
	at org.springframework.boot.context.event.EventPublishingRunListener.starting(EventPublishingRunListener.java:69)
	at org.springframework.boot.SpringApplicationRunListeners.starting(SpringApplicationRunListeners.java:48)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:302)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248)
	at com.example.dubboconsumer.DubboConsumerApplication.main(DubboConsumerApplication.java:13)
 

因爲上面那兩個binding一直都有,我也就沒有在意,覺得應該不是那兩玩意的問題,可能是新加入的pom依賴導致的,把依賴去掉,就沒有這個問題了,那麼剩下就是在依賴裏面找找到底是引入哪個依賴導致,一個一個嘗試發現是tcc-transaction-dubbo,tcc-transaction-core,tcc-transaction-spring這三個依賴導致,就是tcc-transaction三個項目導致,看錯誤提示應該是log4j日誌包衝突導致的,由於dubbo,spring這兩包都依賴了core包,那麼把core包的log4j的依賴exclude即可:

我不知道是哪個包有slf4j,所以全部加上了,還是沒用。然後仔細讀了一下錯誤提示,大概意思是說有兩個類重複了,StaticLoggerBinder.class,slf4j和loggback都有,但是找錯了,找到了slf4j的,錯誤已經提示了:

SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]

自動綁定了slf4j的,其實就是包衝突,那麼把logback的包刪掉就好了。然而這個項目引用了大量的依賴,每一個依賴都有可能自帶了logback,到這裏還是懵懵懂懂的,於是只好查閱了一下源碼,既然是日誌,那肯定與監聽相關,直接找LoggingApplicationListener相關,直接找過去:

啓動時會通過LoggingSystem加載,查看LoggingSystem,
這個systems裏面有三個日誌類,默認讀取的是第一個logback,但是logback和log4j-slf4j都有項目的implement function,即StaticLoggerBinder方法,結果項目自動綁定了StaticLoggerBinder方法,導致加載logback的時候找不到,所以報錯。確認一下,查找一下第一次出錯的地方:

果然,類型不匹配之後就斷言出錯了。那麼現在的問題就只需要把log4j-slj4f包exclude掉即可。然而各個包縱橫交錯,要全部刪掉或者exclude掉很麻煩,使用maven提供的工具,右鍵maven-》show dependencies


紅線的即是版本不一致冗餘的包,找到log4j-slf4j的包exclude就好了,還有一個maven helper也可以。再啓動項目:

好了。測試一下之前的業務,發現又出現新問題了,這次是數據庫問題:

顯示連接到了tcc.order_t的庫上面,很明顯,是加載到錯誤的數據源了,就是剛剛的配置的數據源bean的問題:

顯示id重複,已經有一個了,修改id號。修改完又有問題,tcc-transaction倒是找不到了,所以還是得讀源碼,把tcc-transaction的數據源改了。其實有一個更簡單的方法,使用同一個數據庫即可,但是爲了方便,還是改讀取的bean吧。bean的讀取方式記得有好幾種,可以直接讀xml文件再用getbean方法,也可以用WebApplicationContextUnil來獲取。然而我讀了半天,根本沒有讀入的操作,可能是自動轉配,注意到

這就一目瞭然了,先引進了TransactionRepository,TransactionRepositor再被自動轉配進程序裏面。然後改成dataSource_transaction即可。
接下來就是編寫事務程序了,tcc-transaction由三部分組成,try,confirm,cancel三部分組成,try就是本身的業務:

仿造tcc-transaction的訂單狀態,多添加一個草稿狀態,那麼在下訂單的時候,訂單狀態設置成草稿狀態

在confirm的時候再改成其他的狀態

要注意服務冪等性,服務的冪等性需要confirm,cancel自己實現,所以在確認之前需要判斷是不是草稿狀態,其他就都按部就班了,cancel也是。如果是草稿狀態,那就變成已關閉或者是未支付狀態,如果剛下好的訂單不是草稿狀態,那就是系統出問題了。測試一下出現問題了:

java.lang.RuntimeException: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.transaction.PlatformTransactionManager' available
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.transaction.PlatformTransactionManager' available

提示沒有指定事務管理器,之前是不需要指定的,因爲默認transactionManager就是事務管理器bean的id,不需要指定,可能是事務衝突了,tcc有一個,springboot本身自帶也有一個,那麼就指定一個名字吧

但是還是不行,還是相同的錯誤,後來去找了文檔發現,和dubbo結合是不需要@transactional註解的,去掉就沒事了。再測試就沒有問題了。



日誌都提示成功了。
到這來差不多就實現完成了,總的來說,tcc包含四個組成部分,事務攔截器,事務管理器,事務存儲器,事務處理job
核心就是事務處理器,當事務存儲器存儲了數據之後,事務管理和事務處理只和事務存儲器有關,job對事務數據進行恢復。不管是什麼業務,但凡是需要事務,就會被事務攔截器攔截到,處理後給到事務管理器,事務管理器存儲數據,然後JOB會針對不同事務對事務存儲器的內容做處理,一個處理事務前,一個處理事務後。

閱讀源碼

首先看一下事務存儲器,這個應該是最簡單的了
在core核心包的repository就是存儲器了,提供了緩存,文件,jdbc,zookeeper五種分佈式存儲,上面使用的是jdbc方式,但是FileSystem是很明顯不可取的,因爲分佈式事務是分佈式組件體系裏面的一部分,如果存在本地,那就意味着這個只能在同一個機器裏面的,cache差不多也是本地的意思,Redis可以考慮,zookeeper也不建議,因爲transaction數據變動很大的,zookeeper是強一致性的組件,如果頻繁讀取,那麼對集羣壓力很大。所以一般就是jdbc和redis。
然後其次看一下事務攔截器
首先,攔截器分成兩種,Compensable和Resource兩種,Compensable是註解事務攔截器,resource是資源攔截器,資源是事務裏面很重要的東西,在TCC中try就是用來預留資源的,比如在處理業務的時候,try不會把所有問題都解決掉,會把一部分不能解決的問題的相關數據資源存在庫裏面,加上版本號或者是狀態。地下面的都是事務參與者了。
compensable有兩個,CompensableTransactionAspect和CompensableTransactionInterceptor,CompensableTransactionAspect是一個aop的切面

在帶有compensable註解的方法上切面下去,接下來就是Around方法了


在切點開始和結束都要經過這個Compensable的攔截器。這些註解都是spring的註解。在切點開始結束都會調用 compensableTransactionInterceptor.interceptCompensableMethod(pjp);方法,進去看看

首先傳入一個代理對象,pjp就是代理的目標,然後用getCompensableMethod方法獲取對象的名字,這裏的pjp可以抽象成很多對象,只要帶有了compensable的就是可以被攔截,在本次業務可能就是方法了,那麼getCompensableMethod就是獲得方法的名字,然後getAnnotation獲得註解,之前還以爲是用反射獲取註解,因爲spring基本都是用反射取得包名什麼的。Compensable是事務對象,裏面有一個事務傳播級別,默認是Request,發現如果有事務就用已經有的事務,如果沒有就重啓一個事務。接下來就是compensable.propagation()獲取這個事務的傳播級別了。接下來那句有點看不懂:

        TransactionContext transactionContext = FactoryBuilder.factoryOf(compensable.transactionContextEditor()).getInstance().get(pjp.getTarget(), method, pjp.getArgs());


serialVersionUID是序列化版本號,TransactionXid爲事務ID,attachments存儲子事務,所以這個方法就是獲取事務上下文,也可以說是獲取事務本身。第一次知道事務居然是這樣存儲,還以爲是全部存儲在一個數據庫裏面。然後就是這句代碼了,FactoryOf有點像是工廠模式,剩下就是判斷是否是異步提交。事務處理都說通過事務管理器,而且不同事務之間是有隔離性的,所以接下來就是判斷是不是已經存在一個事務

        boolean isTransactionActive = transactionManager.isTransactionActive();

這一句就是判斷是否有事務存在
具體實現也很簡單,CURRENT就是threadlocal,只要保證線程安全就能保證主從事務的隔離性。接下來這一段很重要,是用來判斷用戶角色的:

        MethodType methodType = CompensableMethodUtils.calculateMethodType(propagation, isTransactionActive, transactionContext);

角色有4種:

但實際上只關心root和Provider兩個,root是主事務,Provider即爲事務參與者,事務參與者也叫分支事務,比如在tcc的sample例子,order就是主事務,redPacket就是分支事務了;我售票系統裏面,buyticket就主事務,裏面的判斷和下訂單都是分支事務。因爲每一個註解了compensable的方法都會進來,所以是需要判斷主從事務。


進入root主事務執行的方法

            transaction = transactionManager.begin();

既然是主事務,那就直接開啓一個全新的事務。首先創建一個事務對象,然後存儲到數據庫,然後把事務把他放在threadlocal裏面,也就是剛剛的CURRENT對象裏面,這個過程也叫註冊。

                returnValue = pjp.proceed();

然後就是執行目標方法了。如果有異常,那就rollback,沒有異常就commit提交,最後別注意要清除,因爲這個是主事務,主事務都執行完了,那麼分支事務肯定也執行完了,所以要在隊列中清除事務。到這裏root要執行的步驟都執行完了,總的來說就那麼幾件事:開啓全局事務,持久化全局事務,註冊全局事務,判斷應該是confirm還是cancel,清除事務。注意commit只是調用自己本身的confirm,不調用子事務的。
分支事務也是一樣:

這個比較簡單,判斷是try,confirm還是cancel,分別執行不同的事情。propagationExitBegin其實就是修改事務狀態。
然後就是Resource資源攔截器了,Resource的資源攔截器的 ResourceCoordinatorAspect也是和compensable一樣
直接看切面後執行的程序,首先從事務管理器獲取當前事務,注意,兩個intercept之間是不能直接傳遞參數的,這裏的intercept也就是compensable和Resource這兩個,所以只能從事務管理器獲取事務對象。confirm和cancel都不執行,只是執行try的數據,到目前爲止,兩個處理器都沒有執行cancel和confirm的方法,前面compensable裏面的confirm和cancel只是改變事務的狀態而已,正在執行我們自定義的confirm和cancel還沒有執行。進入到方法

這確認了事務是try狀態之後要執行的方法,傳入的自然是方法對象了,然後獲取註解,前面有一模一樣的調用:

獲取compensable對象,接着下面就是獲取方法名,而在Java裏面獲取對應的方法唯一的方法是全限定名,也就是類名+包名+方法名,注意在compensable註解裏面只是配置了一個方法名字,所以這兩個confirm和cancel方法只能是和try同一個類下面,否則找不到包名和類名。

然後是獲取事務和事務的編號

判斷是否有一個全局的事務上下文對象了,沒有創建一個新的

接着獲取目標對象的class類,使用反射機制,其實就是一些實現類,比如一些業務裏面的Impl類

接下來就是要準備執行了,前面兩句是保存confirm和cancel執行的上下文,大概就是要給某個人發消息,首先要告訴那個人的電話號碼吧。Participant也是一個事務,上面也提到過另一個類型的事務,差不多,但是這裏要傳入cancel和confirm上下文以及事務的編號,很明顯不是自己執行用的,自己執行直接就執行了,爲什麼要收集這麼多信息,而且也不需要xid,當前事務就能獲取xid的。

果不其然,下面就把所有的信息都給了事務管理器:

**總起來,主要就是處理try階段的事情,並把所有資源封裝,包括了confirm和cancel的上下文信息,分支事務的信息,提交給事務管理器。enlistParticipant不用看了,就是把資源寫進數據庫裏面,所以這個攔截器也沒幹啥,就是把資源放數據庫裏面,更新狀態。**到這裏基本上流程一半完成了,原來的流程是這樣:
CompensableTransactionInterceptor -> ResourceCoordinatorInterceptor -> 事務參與者 -> ResourceCoordinatorInterceptor -> CompensableTransactionInterceptor,又繞回來是因爲兩個攔截器都使用了Around,現在還差最後一個CompensableTransactionInterceptor沒有走,回來看一下Compensable

進來攔截器的時候略過了rollback和commit,前面的流程和進來攔截器的是一樣的,直接看看rollback

獲取當前事務,改變事務狀態爲cancel,更新事務狀態,接下來就是rollback了,cancel方法害得分異步和同步,但是無論是哪個都會執行rollbackTransaction,直接看rollbackTransaction

進去rollback看看

找到所有的子事務,進行rollback操作
在這個方法就執行了我們的cancel或者是commit方法,所有這兩句話是真的去執行了兩個預設的方法。總的來說,CompensableTransactionInterceptor(組織了事務上下文,註冊初始化事務) -> ResourceCoordinatorInterceptor(組織事務參與者等資源) -> 事務參與者 -> ResourceCoordinatorInterceptor(這裏什麼也沒做,因爲Resource只是在try階段做了東西) -> CompensableTransactionInterceptor(執行cancel和confirm)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章