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)載請注明出處颂碧。