分散式事務一:基於資料庫原生分散式事務方案實現
宣告: 本篇主要對所用到的技術做了歸納總結,對原始碼講解較少,如果有基礎的朋友可以直接下載原始碼結合時序圖更能容易理解;基礎比較弱的朋友建議先看看資料自看原始碼這樣更容易理解。這裡的部分資料來源於網路,所以這裡對那些資料提供者表達衷心的感謝。
方案:
- 業務流程:Tss庫向Saas轉移庫存,order為記錄表。
- 技術棧: Springboot+mysql+postgreSQL+atomikos+mybatis
- 專案程式碼地址:github.com/bao17634/sp…
1、分散式事務模型 ACID 實現
1.1、X/Open XA 協議(XA)
最早的分散式事務模型是 X/Open 國際聯盟提出的 X/Open Distributed Transaction Processing(DTP)模型,也就是大家常說的 X/Open XA 協議,簡稱XA 協議。 DTP模型如圖:
- TM:全域性事務管理器
- RM:多個資源管理器
- AP:應用程式
全域性事務管理器負責管理全域性事務狀態與參與的資源,協同資源一起提交或回滾;資源管理器則負責具體的資源操作。 XA 協議主要描述了 TM 與 RM 之間的介面,允許多個資源在同一分散式事務中訪問。 基於 DTP 模型的分散式事務流程大致如下:
XA介面詳解- XA介面時雙向的系統介面,用於事務管理器和多個資源管理器之間形成橋樑。
- 事務管理器控制JTA事務,管理事務生命週期,並協調資源,在JTA中,事務管理器抽象為TransactionManager介面,並通過底層事務服務JTS(java事務服務)實現,資源管理器負責控制和管理實際資源(如資料庫或者JMS(java訊息服務)佇列)。 下圖說明事務管理器、資源管理器,以及JTA環境中客戶應用之間的關係:
1.2、階段提交(2PC)
(1)概念: 二階段提交(Two-phaseCommit)是指,為了使基於分散式系統架構下的所有節點在進行事務提交時保持一致性而設計的一種演演算法(Algorithm),通常二階段提交也被稱為是一種協議。 (2)二階段演演算法思路: 參與者將操作成敗通知協調者,再由協調者根據所有參與者的反饋情報決定各參與者是否要提交操作還是中止操作。 (3)協議假設
- 在分散式系統中,存在一個節點作為協調者,則其他節點作為參與者,且各個節點之間可以進行網路通訊。
- 所有節點都採用預寫式日誌,切日誌被寫入後即被保持在可靠的儲存裝置上,即使節點損壞不會導致日誌資料的消失。
- 所有節點不會永久損失,即使損壞後任然可以恢復。 參與者與協調者之間的關係如圖:(4)第一階段(提交請求階段)
- 協調者節點向所有參與者節點詢問是否可以執行提交操作,並開始等待各參與者節點的響應。
- 參與者節點執行詢問發起為止的所有事務操作,並將資訊寫入日誌。
- 各參與者節點響應協調者節點發起的詢問。如果參與者節點的事務操作實際執行成功,則它返回一個"同意"訊息;如果參與者節點的事務操作實際執行失敗,則它返回一個"中止"訊息。
(5)第二階段(提交執行階段)
- 成功:
當協調者節點從所有參與者節點獲得的相應訊息都為"同意"時:
- 協調者節點向所有參與者節點發出"正式提交"的請求。
- 參與者節點正式完成操作,並釋放在整個事務期間內佔用的資源。
- 參與者節點向協調者節點傳送"完成"訊息。
- 協調者節點收到所有參與者節點反饋的"完成"訊息後,完成事務。
- 失敗
如果任一參與者節點在第一階段返回的響應訊息為"終止",或者 協調者節點在第一階段的詢問超時之前無法獲取所有參與者節點的響應訊息時:
- 協調者節點向所有參與者節點發出"回滾操作"的請求。
- 參與者節點利用之前寫入的日誌資訊執行回滾,並釋放在整個事務期間內佔用的資源。
- 參與者節點向協調者節點傳送"回滾完成"訊息。
- 協調者節點收到所有參與者節點反饋的"回滾完成"訊息後,取消事務。
(6)用事務來解釋二階段 所謂的二階段時將提交過程分為兩個階段:
- 1.準備階段
- 2.提交階段
(7)JTA(Java Transaction API)與atomikos實現分散式事務主要程式碼如下:
- 1> 配置資料來源: DruidXADataSource 連線池實現了XAResource介面用來進行對資源操作
@Bean(name = "systemDataSource")@Primarypublic
DataSource systemDataSource(){
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
ds.setXaProperties(PojoUtil.obj2Properties(postgreSqlProperties));
ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
ds.setUniqueResourceName("systemDataSource");
ds.setPoolSize(5);
ds.setTestQuery("SELECT 1");
return ds;
}
複製程式碼
- 2> 配置事務: 配置spring的JtaTransactionManager,其底層交給atomikos進行事務處理。
@Bean(name = "transactionManager")
@DependsOn({"userTransaction","atomikosTransactionManager"})
public PlatformTransactionManager transactionManager() throws Throwable {
UserTransaction userTransaction = userTransaction();
TransactionManager atomikosTransactionManager = atomikosTransactionManager();
JtaTransactionManager jtaTransactionManager = new
JtaTransactionManager(userTransaction,atomikosTransactionManager);
jtaTransactionManager.setAllowCustomIsolationLevels(true);
return jtaTransactionManager;
}
複製程式碼
2、@Transactional註解式事務詳解
- spring事務管理是指在業務程式碼在 出現異常之後,對之前的操作進行回滾,保證資料庫資料的一致性。
2.1、配置式、註解式事務管理。
(1) 程式設計式:
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def)
try{
businessLogic();
transactionManager.commit(status);
}catch(Exception ex) {
transactionManager. rollback(status);
throw ex;
}
複製程式碼
(2)xml配置式
<tx:advice id="txAdvice" transaction-manager="txManager"><tx:attributes>
<tx:method name="create*" propagation="REQUIRED" timeout="300" rollback-for="java.lang.Exception" />
<tx:method name="delete*" propagation="REQUIRED" timeout="300" rollback-for="java.lang.Exception" />
<tx:method name="update*" propagation="REQUIRED" timeout="300" rollback-for="java.lang.Exception" />
<tx:method name="get*" propagation="REQUIRED" read-only="true" timeout="300" />
<tx:method name="*" propagation="REQUIRED" read-only="true" timeout="300" /></tx:attributes></tx:advice>
<aop:config><aop:pointcut id="txPointcut" expression="execution(* com.mico.emptyspring.service.*ServiceA.*(..))" /><aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice" /></aop:config>
複製程式碼
(3)註解式
在配置檔案中新增配置:<tx:annotation-driven transaction-manager="txManager"/>
或者transaction-manager="txManager
接著在需要事務的方法或類上面新增@Transactional
註解。
2.2、@Transactional配置項
屬性 | 型別 | 描述 |
---|---|---|
value | String | 可選的限定描述符,指定使用的事務管理器 |
propagation | enum: Propagation | 可選的事務傳播行為設定 |
isolation | enum:Isolation | 可選的事務隔離級別設定 |
readOnly | boolean | 讀寫或只讀事務,預設讀寫 |
timeout | int(in seconds granularity) | 事務超時時間設定 |
rollbackFor | Class物件陣列,必須繼承自Throwable | 導致事務回滾的異常類陣列 |
rollbackForClassName | 類名陣列,必須繼承自Throwable | 導致回滾的異常類名字陣列 |
noRollbackFor | Class物件陣列,必須繼承自Throwable | 不會導致事務回滾的異常類陣列 |
noRollbackForClassName | 類名陣列,必須繼承自Throwable | 不會導致事務回滾的異常類名字陣列 |
- value主要是用來指定不同的事務管理器;主要用來滿足在同一個系統中,存在不同的事務管理器。 比如:在Spring中,宣告兩種事務管理器txManager1,txManager2,然後使用者可以根據這個引數來根據需要指定特定的txManager。在一個系統中,需要訪問多個資料來源或者多個資料庫,則必然會配置多個事務管理器。
3、事務隔離級別
3.1、事務的隔離級別(由高到低)
Read uncommitted(讀未提交)
- 所有事務都可以看到其他未提交事務的執行結果。通俗地講就是,在一個事務中可以讀取到另一個事務中新增或修改但未提交的資料。
- 該隔離級別可能導致的問題是髒讀。因為另一個事務可能回滾,所有在第一個事務中讀取到的資料很可能是無效的髒資料,造成髒讀現象。
Read committed(讀提交):
- 一個事務從開始直到提交之前,所做的任何修改對其他事務都是不看見的。
- 該隔離界別可能導致的問題是不可重複讀,因為兩次執行同樣的查詢,可能得到的不一樣的結果。
Repeatable read(重複讀):
- 確保一個事務的多個例項在併發讀取資料時,會看到同樣的資料行。也就是說,可重複讀在一個事務讀取資料,怎麼讀都不發生變化,除非提交了該事務,再次進行讀取。
- 該隔離級別存在的問題就是幻讀。
Serializable(序列化):
- 這是最高的隔離級別
- 他通過強制事務排序,使之不可能相互衝突,從而解決幻讀問題。例如:有兩個事務都操作同一個資料行,那麼這個資料行就會被鎖定,只允許先讀取/操作到資料行的事務優先操作,只有當事務提交了,資料才會解鎖,後一個事務才能操作成功這個資料行,否則只能一直等待。
- 該隔離級別可能導致大量的超市現象和鎖競爭。3.2、隔離級別對併發效能的影響
4、事務的傳播屬性
4.1、解釋: 如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行為,即事務方法的巢狀呼叫會產生事務傳播。
- TransactionDefinition.PROPAGATION_REQUIRED: 如果當前存在事務,則加入該事務;如果當前沒有事務,則建立一個新的事務。這是預設值。
- TransactionDefinition.PROPAGATION_REQUIRES_NEW: 建立一個新的事務,如果當前存在事務,則把當前事務掛起。
- TransactionDefinition.PROPAGATION_SUPPORTS: 如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續執行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事務方式執行,如果當前存在事務,則把當前事務掛起。
- TransactionDefinition.PROPAGATION_NEVER: 以非事務方式執行,如果當前存在事務,則丟擲異常。
- TransactionDefinition.PROPAGATION_MANDATORY: 如果當前存在事務,則加入該事務;如果當前沒有事務,則丟擲異常。
- TransactionDefinition.PROPAGATION_NESTED: 如果當前存在事務,則建立一個事務作為當前事務的巢狀事務來執行;如果當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATION_REQUIRED。
4.2、REQUIRED_NEW和NESTED兩種不同的傳播機制的區別:
(1) REQUIRED_NEW: 事務內部的事務獨立執行,在各自的作用域中,可以獨立的回滾或者提交;而外部的事務將不受內部事務的回滾狀態影響.啟動一個新的,不依賴於環境的 “內部” 事務. 這個事務將被完全 commited 或 rolled back 而不依賴於外部事務,它擁有自己的隔離範圍,自己的鎖,等等. 當內部事務開始執行時,外部事務將被掛起,內務事務結束時,外部事務將繼續執行. (2)NESTED: 事務基於單一的事務來管理,提供了多個儲存點。這種多個儲存點的機制允許內部事務的變更觸發外部事務的回滾。如果外部事務 commit,巢狀事務也會被 commit;如果外部事務 roll back,巢狀事務也會被 roll back 。開始一個 “巢狀的” 事務,它是已經存在事務的一個真正的子事務. 巢狀事務開始執行時,它將取得一個 savepoint. 如果這個巢狀事務失敗,我們將回滾到此 savepoint. 巢狀事務是外部事務的一部分,只有外部事務結束後它才會被提交
5、此方案總結
此方案是基於XA協議的兩階段提交方案,這也一種是傳統的分散式事務方案。 5.1 存在問題:
- 1) 效能問題: 所有參與者在事務提交階段處於同步阻塞狀態,佔用系統資源,容易導致效能瓶頸。
- 2) 可靠性問題: 如果協調者存在單點故障問題,或出現故障,提供者將一直處於鎖定狀態。
- 3) 資料一致性問題: 在階段 2 中,如果出現協調者和參與者都掛了的情況,有可能導致資料不一致。
優點:
- 儘量保證了資料的強一致,適合對資料強一致要求很高的關鍵領域(其實也不能100%保證強一致),適用於低併發效能場景。
缺點:
- 實現複雜,犧牲了可用性,對效能影響較大,不適合高併發高效能場景。