數(shù)據(jù)庫讀寫分離與事務(wù)糾纏的那點坑

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

一個線程在Serivcie時Select時選擇的是從庫芙委,DynamicDataSourceHolder中ThreadLocal對應(yīng)線程存儲的是slave逝嚎,然后調(diào)用Manager時進入事務(wù)锹引,事務(wù)使用默認(rèn)的transacatinManager關(guān)聯(lián)的dataSource渠概,而此時會不會獲取到的是slave逼侦?

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

一個線程在Service層Select數(shù)據(jù)會從數(shù)據(jù)庫獲取一個Connection谚攒,通常來講曙痘,后續(xù)DB的操作在同一線線程會復(fù)用這個DB Connection芳悲,但是從Service進入Manager的事務(wù)后,Get Seq獲取全局唯一標(biāo)識边坤,所以Get Seq一般都會開啟新的事物從DB Pool里重新獲取一個新連接進行操作芭概,但是問題是如果兩個事務(wù)關(guān)聯(lián)的datasource是同一個,即DB Pool是同一個惩嘉,那么如果DB Pool已經(jīng)為空罢洲,是否會造成死鎖?

為了減輕數(shù)據(jù)庫的壓力,一般會進行數(shù)據(jù)庫的讀寫分離惹苗,實現(xiàn)方法一是通過分析sql語句是insert/select/update/delete中的哪一種殿较,從而對應(yīng)選擇主從,二是通過攔截方法名稱的方式來決定主從的桩蓉,如:save()淋纲、insert() 形式的方法使用master庫,select()開頭的使用slave庫院究。

通常在方法上標(biāo)上自定義標(biāo)簽來選擇主從洽瞬。

@DataSource("slave")intqueryForCount(OrderQueryConditionqueryCondition);

或者通過攔截器動態(tài)選擇主從。

<propertyname="methodType">

<mapkey-type="java.lang.String">

<!-- read -->

<entrykey="master"value="find,get,select,count,list,query,stat,show,mine,all,rank,fetch"/>

<!-- write -->

<entrykey="slave"value="save,insert,add,create,update,delete,remove,gain"/></map>

</property>

讀寫動態(tài)庫配置

<beanid="fwmarketDataSource"class="com.jd.fwmarket.datasource.DynamicDataSource"lazy-init="true"><propertyname="targetDataSources"><mapkey-type="java.lang.String"><entrykey="master"value-ref="masterDB"/><entrykey="slave"value-ref="slaveDB"/></map></property><!-- 設(shè)置默認(rèn)的數(shù)據(jù)源业汰,這里默認(rèn)走寫庫 --><propertyname="defaultTargetDataSource"ref="masterDB"/></bean>

DynamicDataSource:

定義動態(tài)數(shù)據(jù)源伙窃,實現(xiàn)通過集成Spring提供的AbstractRoutingDataSource,只需要實現(xiàn)determineCurrentLookupKey方法即可样漆,由于DynamicDataSource是單例的为障,線程不安全的,所以采用ThreadLocal保證線程安全放祟,由DynamicDataSourceHolder完成鳍怨。

publicclassDynamicDataSourceextendsAbstractRoutingDataSource{@OverrideprotectedObjectdetermineCurrentLookupKey(){// 使用DynamicDataSourceHolder保證線程安全,并且得到當(dāng)前線程中的數(shù)據(jù)源keyreturnDynamicDataSourceHolder.getDataSourceKey();}}

DynamicDataSourceHolder類:

publicclassDynamicDataSourceHolder{// 寫庫對應(yīng)的數(shù)據(jù)源keyprivatestaticfinalStringMASTER="master";// 讀庫對應(yīng)的數(shù)據(jù)源keyprivatestaticfinalStringSLAVE="slave";// 使用ThreadLocal記錄當(dāng)前線程的數(shù)據(jù)源keyprivatestaticfinalThreadLocal<String>holder=newThreadLocal<String>();publicstaticvoidputDataSourceKey(Stringkey){holder.set(key);}publicstaticStringgetDataSourceKey(){returnholder.get();}publicstaticvoidmarkDBMaster(){putDataSourceKey(MASTER);}publicstaticvoidmarkDBSlave(){putDataSourceKey(SLAVE);}publicstaticvoidmarkClear(){putDataSourceKey(null);}}

動態(tài)設(shè)置數(shù)據(jù)源可以通過Spring AOP來實現(xiàn)跪妥,而AOP切面的方式也有很多種鞋喇。

Spring AOP的原理:Spring AOP采用動態(tài)代理實現(xiàn),在Spring容器中的bean會被代理對象代替眉撵,代理對象里加入了增強邏輯确徙,當(dāng)調(diào)用代理對象的方法時,目標(biāo)對象的方法就會被攔截执桌。

事務(wù)切面和讀/寫庫選擇切面

<beanid="dataSourceAspect"class="com.jd.fwmarket.service.datasource.DataSourceAspect"/><aop:config><aop:pointcutid="txPointcut"expression="execution(* com.jd.fwmarket.dao..*Impl.*(..))"/><!-- 將切面應(yīng)用到自定義的切面處理器上鄙皇,-9999保證該切面優(yōu)先級最高執(zhí)行 --><aop:aspectref="dataSourceAspect"order="-9999"><aop:beforemethod="before"pointcut-ref="txPointcut"/><aop:aftermethod="after"pointcut-ref="txPointcut"/></aop:aspect></aop:config>

Java邏輯:

publicclassDataSourceAspect{privatestaticfinalString[]defaultSlaveMethodStart=newString[]{"query","find","get","select","count","list"};/**

? ? * 在進入Dao方法之前執(zhí)行

? ? *

? ? * @param point 切面對象

? ? */publicvoidbefore(JoinPointpoint){StringmethodName=point.getSignature().getName();booleanisSlave=isSlave(methodName);if(isSlave){DynamicDataSourceHolder.markDBSlave();}else{DynamicDataSourceHolder.markDBMaster();}}publicvoidafter(){DynamicDataSourceHolder.markClear();}}

使用BeanNameAutoProxyCreator創(chuàng)建代理

<beanid="MySqlDaoSourceInterceptor"class="com.jd.fwmarket.dao.aop.DaoSourceInterceptor">? ? <propertyname="dbType"value="mysql"/>? ? <propertyname="packageName"value="com.jd.fwmarket"/></bean><beanclass="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">? ? <propertyname="beanNames">? ? ? ? <value>*Mapper</value>? ? </property>? ? <propertyname="interceptorNames">? ? ? ? <list>? ? ? ? ? ? <value>MySqlDaoSourceInterceptor</value>? ? ? ? </list>? ? </property></bean>

Java邏輯:

publicclassDaoSourceInterceptorimplementsMethodInterceptor{publicObjectinvoke(MethodInvocation invocation)throws Throwable{dataSourceAspect(invocation);Object result=invocation.proceed();DataSourceHandler.putDataSource(null);returnresult;}privatevoiddataSourceAspect(MethodInvocation invocation){String method=invocation.getMethod().getName();for(String key:ChooseDataSource.METHOD_TYPE_MAP.keySet()){for(Stringtype:ChooseDataSource.METHOD_TYPE_MAP.get(key)){if(method.startsWith(type)){DataSourceHandler.putDataSource(key);return;}}}}}

Spring的事務(wù)處理為了與數(shù)據(jù)訪問解耦,它提供了一套處理數(shù)據(jù)資源的機制仰挣,而這個機制采用ThreadLocal的方式伴逸。

事務(wù)管理器

Spring中通常通過@Transactional來聲明使用事務(wù)。如果@Transactional不指定事務(wù)管理器膘壶,使用缺省错蝴。注意如果Spring容器中定義了兩個事務(wù)管理器,@Transactional標(biāo)注是不支持區(qū)分使用哪個事務(wù)管理器的颓芭,Spring 3.0之后的版本Transactional增加了個string類型的value屬性來特殊指定加以區(qū)分顷锰。

@TransactionalpublicintinsertEntryCreateId(UrpMenu urpMenu){urpMenu.setMId(this.sequenceUtil.get(SequenceConstants.MARKET_URP_MENU));returnsuper.insertEntryCreateId(urpMenu);}

同時進行XML配置

<tx:annotation-driven transaction-manager="transactionManager"proxy-target-class="true"/><bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource"ref="fwmarketDataSource"/></bean>

其中dataSource是在Spring配置文件中定義的數(shù)據(jù)源的對象實例。transaction-manager屬性保存一個對在Spring配置文件中定義的事務(wù)管理器bean的引用亡问,如果沒有它官紫,就會忽略@Transactional注釋立帖,導(dǎo)致代碼不會使用任何事務(wù)讨衣。proxy-target-class控制是基于接口的還是基于類的代理被創(chuàng)建森篷,如果屬性值被設(shè)置為true帜矾,那么基于類的代理將起作用,如果屬性值為false或者被省略毁涉,那么標(biāo)準(zhǔn)的JDK基于接口的代理將起作用沉帮。

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

(推薦閱讀:Spring事務(wù)隔離級別和傳播特性http://www.cnblogs.com/zhishan/p/3195219.html

SQL四類隔離級別

事務(wù)的實現(xiàn)是基于數(shù)據(jù)庫的存儲引擎穆壕。不同的存儲引擎對事務(wù)的支持程度不一樣。Mysql中支持事務(wù)的存儲引擎有InnoDB和NDB其屏。InnoDB是mysql默認(rèn)的存儲引擎喇勋,默認(rèn)的隔離級別是RR(Repeatable Read)。

事務(wù)的隔離性是通過鎖實現(xiàn)漫玄,而事務(wù)的原子性、一致性和持久性則是通過事務(wù)日志實現(xiàn)压彭。

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

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

一個線程在Serivcie時Select時選擇的是從庫睦优,DynamicDataSourceHolder中ThreadLocal對應(yīng)線程存儲的是slave,然后調(diào)用Manager時進入事務(wù)壮不,事務(wù)使用默認(rèn)的transacatinManager關(guān)聯(lián)的dataSource汗盘,而此時會不會獲取到的是slave?

經(jīng)驗證不會询一,但這是因為在AOP設(shè)置動態(tài)織出的時候隐孽,都要清空DynamicDataSourceHolder的ThreadLocal,如此避免了數(shù)據(jù)庫事務(wù)傳播行為影響的主從切換錯誤健蕊。如果Selelct DB從庫完成之后不清空ThreadLocal菱阵,那么ThreadLocal跟線程綁定就會傳播到Transaction,造成事務(wù)操作從庫異常缩功。而清空ThreadLocal之后晴及,Spring的事務(wù)攔截先于動態(tài)數(shù)據(jù)源的判斷,所以事務(wù)會切換成主庫嫡锌,即使事務(wù)中再有查詢從庫的操作虑稼,也不會造成主庫事務(wù)異常。

Q2 事務(wù)隔離級別和傳播特性會不會影響數(shù)據(jù)連接池死鎖

一個線程在Service層Select數(shù)據(jù)會從數(shù)據(jù)庫獲取一個Connection势木,通常來講蛛倦,后續(xù)DB的操作在同一線線程會復(fù)用這個DB Connection,但是從Service進入Manager的事務(wù)后啦桌,Get Seq獲取全局唯一標(biāo)識溯壶,所以Get Seq一般都會開啟新的事物從DB Pool里重新獲取一個新連接進行操作,但是問題是如果兩個事務(wù)關(guān)聯(lián)的datasource是同一個,即DB Pool是同一個茸塞,那么如果DB Pool已經(jīng)為空躲庄,是否會造成死鎖?

經(jīng)驗證會死鎖钾虐,所以在實踐過程中噪窘,如果有此實現(xiàn),建議Get Seq不要使用與事務(wù)同一個連接池效扫【蠹啵或者采用事務(wù)隔離級別設(shè)置PROPAGATION_REQUIRES_NEW進行處理。最優(yōu)的實踐是宎把Get SeqId放到事務(wù)里處理菌仁。

總結(jié)

分析的不是很深浩习,有很多地方還不是特別了解,歡迎吐槽相互學(xué)習(xí)济丘,尤其是說錯了的地方谱秽,一定請幫忙指正,以免誤人子弟摹迷。

在此我向大家推薦一個架構(gòu)學(xué)習(xí)交流群疟赊。交流學(xué)習(xí)群號:833145934 里面資深架構(gòu)師會分享一些整理好的錄制視頻錄像和BATJ面試題:有Spring,MyBatis峡碉,Netty源碼分析近哟,高并發(fā)、高性能鲫寄、分布式吉执、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化地来、分布式架構(gòu)等這些成為架構(gòu)師必備的知識體系戳玫。還能領(lǐng)取免費的學(xué)習(xí)資源,目前受益良多未斑。

作者:AI喬治

鏈接:http://www.reibang.com/p/8b43bbf52dc6

來源:簡書

著作權(quán)歸作者所有量九。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處颂碧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末荠列,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子载城,更是在濱河造成了極大的恐慌肌似,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诉瓦,死亡現(xiàn)場離奇詭異川队,居然都是意外死亡力细,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進店門固额,熙熙樓的掌柜王于貴愁眉苦臉地迎上來眠蚂,“玉大人,你說我怎么就攤上這事斗躏∈呕郏” “怎么了?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵啄糙,是天一觀的道長笛臣。 經(jīng)常有香客問我,道長隧饼,這世上最難降的妖魔是什么沈堡? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮燕雁,結(jié)果婚禮上诞丽,老公的妹妹穿的比我還像新娘。我一直安慰自己拐格,他們只是感情好僧免,可當(dāng)我...
    茶點故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著禁荒,像睡著了一般猬膨。 火紅的嫁衣襯著肌膚如雪角撞。 梳的紋絲不亂的頭發(fā)上呛伴,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天,我揣著相機與錄音谒所,去河邊找鬼热康。 笑死劣领,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播惊暴,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼肄鸽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了逮诲?” 一聲冷哼從身側(cè)響起汛骂,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蝶念,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體廷蓉,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片田度。...
    茶點故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡琢歇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出肥橙,到底是詐尸還是另有隱情存筏,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響耕赘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜册招,卻給世界環(huán)境...
    茶點故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一河质、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乐尊,春花似錦、人聲如沸胁勺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽春贸。三九已至遗遵,卻和暖如春萍恕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背车要。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工允粤, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人翼岁。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓类垫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親琅坡。 傳聞我的和親對象是個殘疾皇子悉患,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,047評論 2 355