1. 程式人生 > >看京東架構師如何解決,數據庫讀寫分離與事務糾纏的坑

看京東架構師如何解決,數據庫讀寫分離與事務糾纏的坑

Java 架構師 微服務 源碼分析

本篇文章討論在數據庫讀寫分離時使用事務的那些坑:

1. 在讀寫分離時會不會造成事務主從切換錯誤

一個線程在Serivcie時Select時選擇的是從庫,DynamicDataSourceHolder中ThreadLocal對應線程存儲的是slave,然後調用Manager時進入事務,事務使用默認的transacatinManager關聯的dataSource,而此時會不會獲取到的是slave?

技術分享圖片

2. 事務隔離級別和傳播特性會不會影響數據連接池死鎖

一個線程在Service層Select數據會從數據庫獲取一個Connection,通常來講,後續DB的操作在同一線線程會復用這個DB Connection,但是從Service進入Manager的事務後,Get Seq獲取全局唯一標識,所以Get Seq一般都會開啟新的事物從DB Pool裏重新獲取一個新連接進行操作,但是問題是如果兩個事務關聯的datasource是同一個,即DB Pool是同一個,那麽如果DB Pool已經為空,是否會造成死鎖?

技術分享圖片

為了減輕數據庫的壓力,一般會進行數據庫的讀寫分離,實現方法一是通過分析sql語句是insert/select/update/delete中的哪一種,從而對應選擇主從,二是通過攔截方法名稱的方式來決定主從的,如:save*()、insert*() 形式的方法使用master庫,select()開頭的使用slave庫。

通常在方法上標上自定義標簽來選擇主從。

技術分享圖片

或者通過攔截器動態選擇主從。

技術分享圖片

讀寫動態庫配置

技術分享圖片

DynamicDataSource:

定義動態數據源,實現通過集成Spring提供的AbstractRoutingDataSource,只需要實現determineCurrentLookupKey方法即可,由於DynamicDataSource是單例的,線程不安全的,所以采用ThreadLocal保證線程安全,由DynamicDataSourceHolder完成。

技術分享圖片

DynamicDataSourceHolder類:

技術分享圖片

動態設置數據源可以通過Spring AOP來實現,而AOP切面的方式也有很多種。

Spring AOP的原理:Spring AOP采用動態代理實現,在Spring容器中的bean會被代理對象代替,代理對象裏加入了增強邏輯,當調用代理對象的方法時,目標對象的方法就會被攔截。

事務切面和讀/寫庫選擇切面

技術分享圖片

Java邏輯:

技術分享圖片

使用BeanNameAutoProxyCreator創建代理

技術分享圖片

Java邏輯:

技術分享圖片

Spring的事務處理為了與數據訪問解耦,它提供了一套處理數據資源的機制,而這個機制采用ThreadLocal的方式。

事務管理器

Spring中通常通過@Transactional來聲明使用事務。如果@Transactional不指定事務管理器,使用缺省。註意如果Spring容器中定義了兩個事務管理器,@Transactional標註是不支持區分使用哪個事務管理器的,Spring 3.0之後的版本Transactional增加了個string類型的value屬性來特殊指定加以區分。

技術分享圖片

同時進行XML配置

技術分享圖片

其中dataSource是在Spring配置文件中定義的數據源的對象實例。transaction-manager屬性保存一個對在Spring配置文件中定義的事務管理器bean的引用,如果沒有它,就會忽略@Transactional註釋,導致代碼不會使用任何事務。proxy-target-class控制是基於接口的還是基於類的代理被創建,如果屬性值被設置為true,那麽基於類的代理將起作用,如果屬性值為false或者被省略,那麽標準的JDK基於接口的代理將起作用。

註意@Transactional建議在具體的類(或類的方法)上使用,不要使用在類所要實現的任何接口上。

SQL四類隔離級別

事務的實現是基於數據庫的存儲引擎。不同的存儲引擎對事務的支持程度不一樣。Mysql中支持事務的存儲引擎有InnoDB和NDB。InnoDB是mysql默認的存儲引擎,默認的隔離級別是RR(Repeatable Read)。

事務的隔離性是通過鎖實現,而事務的原子性、一致性和持久性則是通過事務日誌實現。

(推薦閱讀:數據庫事務與MySQL事務總結 https://zhuanlan.zhihu.com/p/29166694)

Q1 在讀寫分離時會不會造成事務主從切換錯誤

一個線程在Serivcie時Select時選擇的是從庫,DynamicDataSourceHolder中ThreadLocal對應線程存儲的是slave,然後調用Manager時進入事務,事務使用默認的transacatinManager關聯的dataSource,而此時會不會獲取到的是slave?

經驗證不會,但這是因為在AOP設置動態織出的時候,都要清空DynamicDataSourceHolder的ThreadLocal,如此避免了數據庫事務傳播行為影響的主從切換錯誤。如果Selelct DB從庫完成之後不清空ThreadLocal,那麽ThreadLocal跟線程綁定就會傳播到Transaction,造成事務操作從庫異常。而清空ThreadLocal之後,Spring的事務攔截先於動態數據源的判斷,所以事務會切換成主庫,即使事務中再有查詢從庫的操作,也不會造成主庫事務異常。

技術分享圖片

Q2 事務隔離級別和傳播特性會不會影響數據連接池死鎖

一個線程在Service層Select數據會從數據庫獲取一個Connection,通常來講,後續DB的操作在同一線線程會復用這個DB Connection,但是從Service進入Manager的事務後,Get Seq獲取全局唯一標識,所以Get Seq一般都會開啟新的事物從DB Pool裏重新獲取一個新連接進行操作,但是問題是如果兩個事務關聯的datasource是同一個,即DB Pool是同一個,那麽如果DB Pool已經為空,是否會造成死鎖?

經驗證會死鎖,所以在實踐過程中,如果有此實現,建議Get Seq不要使用與事務同一個連接池。或者采用事務隔離級別設置PROPAGATION_REQUIRES_NEW進行處理。最優的實踐是宎把Get SeqId放到事務裏處理。

技術分享圖片

總結

分析的不是很深,有很多地方還不是特別了解,歡迎吐槽相互學習,尤其是說錯了的地方,一定請幫忙指正,以免誤人子弟。

針對上面的技術我特意整理了一下,有很多技術不是靠幾句話能講清楚,所以幹脆找朋友錄制了一些視頻,很多問題其實答案很簡單,但是背後的思考和邏輯不簡單,要做到知其然還要知其所以然。如果想學習Java工程化、高性能及分布式、深入淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友可以加我的Java進階群:744642380,群裏有阿裏大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給大家


看京東架構師如何解決,數據庫讀寫分離與事務糾纏的坑