1. 在讀寫分離時(shí)會(huì)不會(huì)造成事務(wù)主從切換錯(cuò)誤
一個(gè)線程在Serivcie時(shí)Select時(shí)選擇的是從庫(kù),DynamicDataSourceHolder中ThreadLocal對(duì)應(yīng)線程存儲(chǔ)的是slave,然后調(diào)用Manager時(shí)進(jìn)入事務(wù),事務(wù)使用默認(rèn)的transacatinManager關(guān)聯(lián)的dataSource,而此時(shí)會(huì)不會(huì)獲取到的是slave?

2. 事務(wù)隔離級(jí)別和傳播特性會(huì)不會(huì)影響數(shù)據(jù)連接池死鎖
一個(gè)線程在Service層Select數(shù)據(jù)會(huì)從數(shù)據(jù)庫(kù)獲取一個(gè)Connection,通常來(lái)講,后續(xù)DB的操作在同一線線程會(huì)復(fù)用這個(gè)DB Connection,但是從Service進(jìn)入Manager的事務(wù)后,Get Seq獲取全局唯一標(biāo)識(shí),所以Get Seq一般都會(huì)開(kāi)啟新的事物從DB Pool里重新獲取一個(gè)新連接進(jìn)行操作,但是問(wèn)題是如果兩個(gè)事務(wù)關(guān)聯(lián)的datasource是同一個(gè),即DB Pool是同一個(gè),那么如果DB Pool已經(jīng)為空,是否會(huì)造成死鎖?

為了減輕數(shù)據(jù)庫(kù)的壓力,一般會(huì)進(jìn)行數(shù)據(jù)庫(kù)的讀寫分離,實(shí)現(xiàn)方法一是通過(guò)分析sql語(yǔ)句是insert/select/update/delete中的哪一種,從而對(duì)應(yīng)選擇主從,二是通過(guò)攔截方法名稱的方式來(lái)決定主從的,如:save()、insert() 形式的方法使用master庫(kù),select()開(kāi)頭的使用slave庫(kù)。
通常在方法上標(biāo)上自定義標(biāo)簽來(lái)選擇主從。
@DataSource("slave")intqueryForCount(OrderQueryConditionqueryCondition);
或者通過(guò)攔截器動(dòng)態(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>
讀寫動(dòng)態(tài)庫(kù)配置
<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)走寫庫(kù) --><propertyname="defaultTargetDataSource"ref="masterDB"/></bean>
DynamicDataSource:
定義動(dòng)態(tài)數(shù)據(jù)源,實(shí)現(xiàn)通過(guò)集成Spring提供的AbstractRoutingDataSource,只需要實(shí)現(xiàn)determineCurrentLookupKey方法即可,由于DynamicDataSource是單例的,線程不安全的,所以采用ThreadLocal保證線程安全,由DynamicDataSourceHolder完成。
publicclassDynamicDataSourceextendsAbstractRoutingDataSource{@OverrideprotectedObjectdetermineCurrentLookupKey(){// 使用DynamicDataSourceHolder保證線程安全,并且得到當(dāng)前線程中的數(shù)據(jù)源keyreturnDynamicDataSourceHolder.getDataSourceKey();}}
DynamicDataSourceHolder類:
publicclassDynamicDataSourceHolder{// 寫庫(kù)對(duì)應(yīng)的數(shù)據(jù)源keyprivatestaticfinalStringMASTER="master";// 讀庫(kù)對(duì)應(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);}}
動(dòng)態(tài)設(shè)置數(shù)據(jù)源可以通過(guò)Spring AOP來(lái)實(shí)現(xiàn),而AOP切面的方式也有很多種。
Spring AOP的原理:Spring AOP采用動(dòng)態(tài)代理實(shí)現(xiàn),在Spring容器中的bean會(huì)被代理對(duì)象代替,代理對(duì)象里加入了增強(qiáng)邏輯,當(dāng)調(diào)用代理對(duì)象的方法時(shí),目標(biāo)對(duì)象的方法就會(huì)被攔截。
事務(wù)切面和讀/寫庫(kù)選擇切面
<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)先級(jí)最高執(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"};/**
? ? * 在進(jìn)入Dao方法之前執(zhí)行
? ? *
? ? * @param point 切面對(duì)象
? ? */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ù)訪問(wèn)解耦,它提供了一套處理數(shù)據(jù)資源的機(jī)制,而這個(gè)機(jī)制采用ThreadLocal的方式。
事務(wù)管理器
Spring中通常通過(guò)@Transactional來(lái)聲明使用事務(wù)。如果@Transactional不指定事務(wù)管理器,使用缺省。注意如果Spring容器中定義了兩個(gè)事務(wù)管理器,@Transactional標(biāo)注是不支持區(qū)分使用哪個(gè)事務(wù)管理器的,Spring 3.0之后的版本Transactional增加了個(gè)string類型的value屬性來(lái)特殊指定加以區(qū)分。
@TransactionalpublicintinsertEntryCreateId(UrpMenu urpMenu){urpMenu.setMId(this.sequenceUtil.get(SequenceConstants.MARKET_URP_MENU));returnsuper.insertEntryCreateId(urpMenu);}
同時(shí)進(jìn)行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ù)源的對(duì)象實(shí)例。transaction-manager屬性保存一個(gè)對(duì)在Spring配置文件中定義的事務(wù)管理器bean的引用,如果沒(méi)有它,就會(huì)忽略@Transactional注釋,導(dǎo)致代碼不會(huì)使用任何事務(wù)。proxy-target-class控制是基于接口的還是基于類的代理被創(chuàng)建,如果屬性值被設(shè)置為true,那么基于類的代理將起作用,如果屬性值為false或者被省略,那么標(biāo)準(zhǔn)的JDK基于接口的代理將起作用。
注意@Transactional建議在具體的類(或類的方法)上使用,不要使用在類所要實(shí)現(xiàn)的任何接口上。
(推薦閱讀:Spring事務(wù)隔離級(jí)別和傳播特性http://www.cnblogs.com/zhishan/p/3195219.html)
SQL四類隔離級(jí)別
事務(wù)的實(shí)現(xiàn)是基于數(shù)據(jù)庫(kù)的存儲(chǔ)引擎。不同的存儲(chǔ)引擎對(duì)事務(wù)的支持程度不一樣。Mysql中支持事務(wù)的存儲(chǔ)引擎有InnoDB和NDB。InnoDB是mysql默認(rèn)的存儲(chǔ)引擎,默認(rèn)的隔離級(jí)別是RR(Repeatable Read)。
事務(wù)的隔離性是通過(guò)鎖實(shí)現(xiàn),而事務(wù)的原子性、一致性和持久性則是通過(guò)事務(wù)日志實(shí)現(xiàn)。
(推薦閱讀:數(shù)據(jù)庫(kù)事務(wù)與MySQL事務(wù)總結(jié)https://zhuanlan.zhihu.com/p/29166694)
Q1 在讀寫分離時(shí)會(huì)不會(huì)造成事務(wù)主從切換錯(cuò)誤
一個(gè)線程在Serivcie時(shí)Select時(shí)選擇的是從庫(kù),DynamicDataSourceHolder中ThreadLocal對(duì)應(yīng)線程存儲(chǔ)的是slave,然后調(diào)用Manager時(shí)進(jìn)入事務(wù),事務(wù)使用默認(rèn)的transacatinManager關(guān)聯(lián)的dataSource,而此時(shí)會(huì)不會(huì)獲取到的是slave?
經(jīng)驗(yàn)證不會(huì),但這是因?yàn)樵贏OP設(shè)置動(dòng)態(tài)織出的時(shí)候,都要清空DynamicDataSourceHolder的ThreadLocal,如此避免了數(shù)據(jù)庫(kù)事務(wù)傳播行為影響的主從切換錯(cuò)誤。如果Selelct DB從庫(kù)完成之后不清空ThreadLocal,那么ThreadLocal跟線程綁定就會(huì)傳播到Transaction,造成事務(wù)操作從庫(kù)異常。而清空ThreadLocal之后,Spring的事務(wù)攔截先于動(dòng)態(tài)數(shù)據(jù)源的判斷,所以事務(wù)會(huì)切換成主庫(kù),即使事務(wù)中再有查詢從庫(kù)的操作,也不會(huì)造成主庫(kù)事務(wù)異常。

Q2 事務(wù)隔離級(jí)別和傳播特性會(huì)不會(huì)影響數(shù)據(jù)連接池死鎖
一個(gè)線程在Service層Select數(shù)據(jù)會(huì)從數(shù)據(jù)庫(kù)獲取一個(gè)Connection,通常來(lái)講,后續(xù)DB的操作在同一線線程會(huì)復(fù)用這個(gè)DB Connection,但是從Service進(jìn)入Manager的事務(wù)后,Get Seq獲取全局唯一標(biāo)識(shí),所以Get Seq一般都會(huì)開(kāi)啟新的事物從DB Pool里重新獲取一個(gè)新連接進(jìn)行操作,但是問(wèn)題是如果兩個(gè)事務(wù)關(guān)聯(lián)的datasource是同一個(gè),即DB Pool是同一個(gè),那么如果DB Pool已經(jīng)為空,是否會(huì)造成死鎖?
經(jīng)驗(yàn)證會(huì)死鎖,所以在實(shí)踐過(guò)程中,如果有此實(shí)現(xiàn),建議Get Seq不要使用與事務(wù)同一個(gè)連接池。或者采用事務(wù)隔離級(jí)別設(shè)置PROPAGATION_REQUIRES_NEW進(jìn)行處理。最優(yōu)的實(shí)踐是宎把Get SeqId放到事務(wù)里處理。

總結(jié)
分析的不是很深,有很多地方還不是特別了解,歡迎吐槽相互學(xué)習(xí),尤其是說(shuō)錯(cuò)了的地方,一定請(qǐng)幫忙指正,以免誤人子弟。
在此我向大家推薦一個(gè)架構(gòu)學(xué)習(xí)交流群。交流學(xué)習(xí)群號(hào):833145934 里面資深架構(gòu)師會(huì)分享一些整理好的錄制視頻錄像和BATJ面試題:有Spring,MyBatis,Netty源碼分析,高并發(fā)、高性能、分布式、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化、分布式架構(gòu)等這些成為架構(gòu)師必備的知識(shí)體系。還能領(lǐng)取免費(fèi)的學(xué)習(xí)資源,目前受益良多。
作者:AI喬治
鏈接:http://www.itdecent.cn/p/8b43bbf52dc6
來(lái)源:簡(jiǎn)書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。