1. 程式人生 > >Spring分散式動態資料來源+事務

Spring分散式動態資料來源+事務

本篇是一個真實案例,來源於與一位技術達人一起共同探討,解決一個分散式事務中,如何動態管理,

切換資料庫,適用於大型網際網路,及分庫管理系統。網上雖有很多相似問題,但卻無可靠,有效之

解決方案。

1. 背景介紹 

背景簡單介紹一下。一位技術達人(同學)碰到一個棘手問題,聽來較為吸引,恰巧手中略空,就一

起研究一下,給自己2個半小時定位

案例來源於一中大型網際網路專案,涉及多個分散式資料庫(根據業務分拆的資料庫,幾十個之多MySQL),

使用Spring, MyBatis等流行技術,業務操作需要跨多個數據庫,並且整個業務需要事務支援。聽起來不錯。


目前碰到問題,分散式事務選擇開源輕量級Atomikos,動態管理資料來源選擇Spring的AbstractRoutingDataSource,

一切正常。問題出在當事務於動態切換資料來源出現在一個服務中的時候,資料來源無法動態切換了。


問題重現

問題描述至此,帶著濃濃好奇心,我們開始了神奇之旅。2. 問題重現 聽完達人描述好,好奇心突發,

苦於遠端協助,只好先現場還原,問題重現了。
我們簡單模擬一下了,模擬簡單業務場景,在一個方法中需要動態連線兩個資料庫,並分別插入記錄,

提交,整個過程需要事務支援。
定義2個MySQL資料庫,Spring+Transaction, JDBC(簡單起見)。


我們建立UserDAO:


簡單明瞭,純手工JDBCTemplate。
下面我們定義動態資料來源,藉助Spring提供的AbstractRoutingDataSource可以幫助我們動態切換資料來源:

我們需要自己定一個執行緒級別的Holder來儲存當前連線資料來源,並返回給RoutingDataSource:
可以看到我們定義了一個ThreadLocal級別變數,目的是支援併發多執行緒,並把當前資料來源作為動態變數。 這樣與RoutingDataSource結合後,Spring就可以動態發現當前的資料來源Spring Bean ID。

Spring配置檔案中,定義了2個數據源DataSource1, DataSource2, 以及一個公共的DataSource集成了 所有動態資料來源以及一個預設數據源。並配置DataSourceTransactionManager事務支援。 至此看起來一切順利,看一下執行效果如何:

一起看起都是這麼美好。我們新增事務。
看一下這次的執行結果:

好吧,事務一新增,居然導致db1插入兩條記錄,而db2完全沒有記錄插入。
好的方面是我們案件重現。3. 庖丁解牛  問題重現後,我們就需要庖丁解牛了。其實大概網路搜尋一下
就會知道玄關了。問題顯然出在那一行@Transactional為什麼一加事務,方法就只訪問一個數據源了呢? 我們來看一下事務的具體原始碼吧, 我們這裡使用的是DataSourceTransactionManager:
原來在開啟事務的時候,因為我們使用的是DataSourceTransactionManager, Spring會預設馬上去取得資料來源, 並且把它快取到DataSourceTransactionObject物件中,用於後續的commit, rollback等事務操作,所以我們 後續儘管切換AbstractRoutingDataSource, 對事務已然無效。所以上文中才會出現db1插入兩次,而db2沒有 一條記錄。現在明白了?
解決方案  問題真正原因搞清楚後,解決方案就比價容易了,當然,網上似乎仍未有切實可行的完美方案。粗略來看,似乎手工管理事務,並 且動態修改資料來源應該可行。試試看:

手工管理事務,並且暴力直接修改DAO資料來源,這樣應該沒什麼大問題,執行一下:
Bingo! 看看能否會滾?我們故意在db1結束後,丟擲一個異常,看看是否最終全部會滾?
如我們期待中,db1也回滾:
好,不錯。事情總算水落石出,也有了一些粗略的解決方案。總結

此方案到此告一斷落,雖然只是一個簡單的POC, 稱不上優美,更談不上完美,這裡只是拋磚引玉,主要來

分享整個案例的重現,分析,解決思路而已,至於達人用到的分散式事務則大同小異,另外對於MyBatis可能

需要暴力修改SqlSessionFactory了,這裡就不贅述了。

本篇分散式事務後來發現有點問題,所以又後續寫了系列2.請關注。
http://blog.csdn.net/erixhao/article/details/52138760

公眾號:技術極客TechBooster