使用Spring基于應(yīng)用實(shí)現(xiàn)讀寫分離

? ? ? ? 大部分應(yīng)用都是讀多寫少,也就說對(duì)數(shù)據(jù)庫(kù)讀取數(shù)據(jù)的壓力比較大。我們可以采用讀寫分離的模式減少數(shù)據(jù)庫(kù)讀的壓力。基于應(yīng)用層的讀寫分離方案,多數(shù)據(jù)源切換方便,由程序自動(dòng)完成。下面是基于應(yīng)用的讀寫分離方案的模式:

基于應(yīng)用的讀寫分離方案模式

解決方案

一、原理

原理圖

? ? ? ? 在進(jìn)入service之前,使用AOP來作出判斷,是使用寫庫(kù)還是使用讀庫(kù),判斷依據(jù)可以根據(jù)方法名進(jìn)行判斷,比如說以query、find、get等開頭的就走讀庫(kù),其他的走寫庫(kù)。

二、具體代碼實(shí)現(xiàn)

1、DynamicDataSource

由于DynamicDataSource是單例的,線程不安全的,所以采用ThreadLocal來保證線程安全,由DynamicDataSourceHolder完成。

public class?DynamicDataSource?extends AbstractRoutingDataSource{

? ??????@Override

? ??????protected Object determineCurrentLookupKey() {?

? ? ? ? ? ? ? ? //使用DynamicDataSourceHolder保證線程安全,并且得到當(dāng)前線程中的數(shù)據(jù)源key

? ??????????????return DynamicDataSourceHolder.getDataSourceKey();

? ??????}?

}


2、DynamicDataSourceHolder

public class?DynamicDataSourceHolder{

? ??????? //寫庫(kù)對(duì)應(yīng)的數(shù)據(jù)源key

? ? ? ?private static final String MASTER = "master";

? ? ? ? ?//讀庫(kù)對(duì)應(yīng)的數(shù)據(jù)源key?

? ??????private static final String SLAVE = "slave"; ?

? ??????//使用ThreadLocal記錄當(dāng)前線程的數(shù)據(jù)源key

? ??????private static final ThreadLocal<String> holder = new ThreadLocal<String>();?

? ? ? ? ?//設(shè)置數(shù)據(jù)源

? ??????public static void putDataSourceKey(String key) {? ? ? ? ?

? ??????????????????holder.set(key);? ? ??

? ??????}?

? ? ? ? //獲取數(shù)據(jù)源

? ? ? ? public static String getDataSourceKey() {? ? ? ? ? ? ??

? ??????????????????return holder.get();?

? ? ? ? ?}

? ? ? ? //標(biāo)記寫數(shù)據(jù)庫(kù)

? ??????public static void markMaster(){? ? ? ??

? ??????????????????putDataSourceKey(MASTER);? ? ?

? ??????}??

? ? ? ? //標(biāo)記讀數(shù)據(jù)庫(kù)

? ? ? ??public static void markSlave(){? ? ? ? ? ? ??

? ??????????????????putDataSourceKey(SLAVE);

? ? ? ? }

}

3、DataSourceAspect

定義數(shù)據(jù)源的AOP切面,通過該Service的方法名判斷是應(yīng)該走讀庫(kù)還是寫庫(kù)?

public class DataSourceAspect{

? ? ? ? //在進(jìn)入Service方法之前執(zhí)行

? ??????public void before(JoinPoint point) {

? ??????????????// 獲取到當(dāng)前執(zhí)行的方法名?

? ? ? ? ? ? ? ? ?String methodName = point.getSignature().getName();?

? ? ? ? ? ? ? ? ?if (isSlave(methodName)) {

? ??????????????????????????// 標(biāo)記為讀庫(kù)

? ??????????????????????????DynamicDataSourceHolder.markSlave();

? ? ? ? ? ? ? ? ? }else{

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 標(biāo)記為寫庫(kù)

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?DynamicDataSourceHolder.markMaster();

? ? ? ? ? ? ? ? ? }

????????}

? ? ? ? //判斷是否走讀庫(kù)

? ??????private Boolean isSlave(String methodName) {? ? ? ? ??

? ???????????????// 方法名以query、find、get開頭的方法名走從庫(kù)? ? ? ? ? ? ? }?

? ? ? ? ? ? ? ? ?return StringUtils.startsWithAny(methodName, "query", "find", "get");

? ? ? ? }

}

4、配置2個(gè)數(shù)據(jù)源

jdbc.master.driver=com.mysql.jdbc.Driver

jdbc.master.url=jdbc:mysql://127.0.0.1:3306/mybatis_1128useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true

jdbc.master.username=root

jdbc.master.password=123456??

jdbc.slave01.driver=com.mysql.jdbc.Driver

jdbc.slave01.url=jdbc:mysql://127.0.0.1:3307/mybatis_1128useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true

jdbc.slave01.username=root

jdbc.slave01.password=123456

5、定義連接池

<!-- 配置連接池 -->???

<bean id="masterDataSource" class="com.jolbox.bonecp.BoneCPDataSource"?????? destroy-method="close">??????

????????<!-- 數(shù)據(jù)庫(kù)驅(qū)動(dòng) -->??????

????????<property name="driverClass"value="${jdbc.master.driver}" />??????

????????<!-- 相應(yīng)驅(qū)動(dòng)的jdbcUrl -->??????

????????<property name="jdbcUrl"value="${jdbc.master.url}" />??????

????????<!-- 數(shù)據(jù)庫(kù)的用戶名 -->??????

????????<property name="username"value="${jdbc.master.username}" />??????

????????<!-- 數(shù)據(jù)庫(kù)的密碼 -->??????

? ? ? ? <property name="password"value="${jdbc.master.password}" />??????

????????<!-- 檢查數(shù)據(jù)庫(kù)連接池中空閑連接的間隔時(shí)間,單位是分,默認(rèn)值:240,如果要取消則設(shè)置為0 -->? ? ? ? ?

????????property name="idleConnectionTestPeriod"value="60" />??????

????????<!-- 連接池中未使用的鏈接最大存活時(shí)間,單位是分,默認(rèn)值:60,如果要永遠(yuǎn)存活設(shè)置為0 -->??????

????????<property name="idleMaxAge"value="30" />??????

????????<!-- 每個(gè)分區(qū)最大的連接數(shù) -->??????

????????<property name="maxConnectionsPerPartition"value="150" />??????

????????<!-- 每個(gè)分區(qū)最小的連接數(shù) -->??????

????????<property name="minConnectionsPerPartition"value="5" />???

</bean>

<!-- 配置連接池 -->

<bean id="slave01DataSource" class="com.jolbox.bonecp.BoneCPDataSource" ?????? destroy-method="close">

? ??????<!-- 數(shù)據(jù)庫(kù)驅(qū)動(dòng) -->

? ??????<property name="driverClass" value="${jdbc.slave01.driver}" />

? ??????<!-- 相應(yīng)驅(qū)動(dòng)的jdbcUrl -->?

? ??????<property name="jdbcUrl" value="${jdbc.slave01.url}" />

? ??????<!-- 數(shù)據(jù)庫(kù)的用戶名 -->?

? ??????<property name="username" value="${jdbc.slave01.username}" />

? ??????<!-- 數(shù)據(jù)庫(kù)的密碼 -->

? ??????<property name="password" value="${jdbc.slave01.password}" />

? ??????<!-- 檢查數(shù)據(jù)庫(kù)連接池中空閑連接的間隔時(shí)間,單位是分,默認(rèn)值:240,如果要取消則設(shè)置為0 -->

? ? ? ? ?<property name="idleConnectionTestPeriod" value="60" />

? ??????<!-- 連接池中未使用的鏈接最大存活時(shí)間,單位是分,默認(rèn)值:60,如果要永遠(yuǎn)存活設(shè)置為0 -->

? ??????<property name="idleMaxAge" value="30" />

? ??????<!-- 每個(gè)分區(qū)最大的連接數(shù) -->

? ??????<property name="maxConnectionsPerPartition" value="150" />

? ? ? ? ?<!-- 每個(gè)分區(qū)最小的連接數(shù) -->

? ? ? ? ?<property name="minConnectionsPerPartition" value="5" />?

</bean>

6、定義DataSource

<!-- 定義數(shù)據(jù)源,使用自己實(shí)現(xiàn)的數(shù)據(jù)源 -->???

<bean id="dataSource" class="cn.itcast.usermanage.spring.DynamicDataSource">??????

????????<!-- 設(shè)置多個(gè)數(shù)據(jù)源 -->??????

? ? ? ?<property name="targetDataSources">??????????

? ? ? ?????<map key-type="java.lang.String">?????????????

????????????????<!-- 這個(gè)key需要和程序中的key一致 -->?????????????

????????????????<entry key="master"value-ref="masterDataSource"/>?????????????

????????????????<entry key="slave"value-ref="slave01DataSource"/>??????????

????????????</map>??????

????????</property>??????

????????<!-- 設(shè)置默認(rèn)的數(shù)據(jù)源,這里默認(rèn)走寫庫(kù) -->??????

????????<property name="defaultTargetDataSource"ref="masterDataSource"/>???

</bean>

7、定義事務(wù)管理器

<bean id="transactionManager"?????? class="org.springframework.jdbc.datasource.DataSourceTransactionManager">? ? ? ? ?

? ??????<property name="dataSource" ref="dataSource" />? ? ?

</bean>? ?

8、定義事務(wù)策略

<!-- 定義事務(wù)策略 -->???

<tx:advice id="txAdvice" transaction-manager="transactionManager">??????

????????<tx:attributes>??????????

????????????<!--定義查詢方法都是只讀的 -->??????????

????????????<tx:method name="query*"read-only="true" />??????????

????????????<tx:method name="find*" read-only="true" />??????????

????????????<tx:method name="get*"read-only="true" />???????????

????????????<!-- 主庫(kù)執(zhí)行操作,事務(wù)傳播行為定義為默認(rèn)行為 -->??????????

????????????<tx:method name="save*"propagation="REQUIRED" />??????????

????????????<tx:method name="update*"propagation="REQUIRED" />??????????

????????????<tx:method name="delete*"propagation="REQUIRED" />???????????

????????????<!--其他方法使用默認(rèn)事務(wù)策略 -->??????????

????????????<tx:method name="*"/>??????

????????</tx:attributes>???

</tx:advice>

9、定義切面

<!-- 定義AOP切面處理器 -->???

<bean class="cn.itcast.usermanage.spring.DataSourceAspect"id="dataSourceAspect" />????

<aop:config>??????

????????<!-- 定義切面,所有的service的所有方法 -->??????

????????<aop:pointcut? id="txPointcut"? expression="execution(* xx.xxx.xxxxxxx.service.*.*(..))"/>??????

????????<!-- 應(yīng)用事務(wù)策略到Service切面 -->??????

????????<aop:advisor advice-ref="txAdvice"? pointcut-ref="txPointcut"/>? ? ? ? ? ? ? ? ?

????????<!-- 將切面應(yīng)用到自定義的切面處理器上,-9999保證該切面優(yōu)先級(jí)最高執(zhí)行 -->??????

????????<aop:aspect ref="dataSourceAspect"order="-9999">??????????

????????????????<aop:before method="before"pointcut-ref="txPointcut" />??????

????????</aop:aspect>???

</aop:config>

? ? ? ? 至此,簡(jiǎn)單的基于應(yīng)用層的讀寫分離就實(shí)現(xiàn)了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容