天天看點

在應用層通過spring特性解決資料庫讀寫分離

1、應用層

<a target="_blank" href="http://neoremind.net/2011/06/spring%E5%AE%9E%E7%8E%B0%E6%95%B0%E6%8D%AE%E5%BA%93%E8%AF%BB%E5%86%99%E5%88%86%E7%A6%BB">http://neoremind.net/2011/06/spring實作資料庫讀寫分離</a>

目前的一些解決方案需要在程式中手動指定資料源,比較麻煩,後邊我會通過AOP思想來解決這個問題。

2、中間件

此處我們介紹一種在應用層的解決方案,通過spring動态資料源和AOP來解決資料庫的讀寫分離。

該方案目前已經在一個網際網路項目中使用了,而且可以很好的工作。

一讀多寫;當寫時預設讀操作到寫庫、當寫時強制讀操作到讀庫。

讀庫負載均衡、讀庫故障轉移等。

不想引入中間件,想在應用層解決讀寫分離,可以考慮這個方案;

建議資料通路層使用jdbc、ibatis,不建議hibernate。

應用層解決,不引入額外中間件;

在應用層支援『當寫時預設讀操作到寫庫』,這樣如果我們采用這種方案,在寫操作後讀資料直接從寫庫拿,不會産生資料複制的延遲問題;

應用層解決讀寫分離,理論支援任意資料庫。

1、不支援@Transactional注解事務,此方案要求所有讀方法必須是read-only=true,是以如果是@Transactional,這樣就要求在每一個讀方法頭上加@Transactional 且readOnly屬性=true,相當麻煩。 :oops: 

2、必須按照配置約定進行配置,不夠靈活。

在應用層通過spring特性解決資料庫讀寫分離

方案1:當隻有讀操作的時候,直接操作讀庫(從庫);

        當在寫事務(即寫主庫)中讀時,也是讀主庫(即參與到主庫操作),這樣的優勢是可以防止寫完後可能讀不到剛才寫的資料;

此方案其實是使用事務傳播行為為:SUPPORTS解決的。

在應用層通過spring特性解決資料庫讀寫分離

方案2:當隻有讀操作的時候,直接操作讀庫(從庫);

        當在寫事務(即寫主庫)中讀時,強制走從庫,即先暫停寫事務,開啟讀(讀從庫),然後恢複寫事務。

此方案其實是使用事務傳播行為為:NOT_SUPPORTS解決的。

cn.javass.common.datasource.ReadWriteDataSource:讀寫分離的動态資料源,類似于AbstractRoutingDataSource,具體參考javadoc;

cn.javass.common.datasource.ReadWriteDataSourceDecision:讀寫庫選擇的決策者,具體參考javadoc;

cn.javass.common.datasource.ReadWriteDataSourceProcessor:此類實作了兩個職責(為了減少類的數量将兩個功能合并到一起了):讀/寫動态資料庫選擇處理器、通過AOP切面實作讀/寫選擇,具體參考javadoc。

1、資料源配置

1.1、寫庫配置

在應用層通過spring特性解決資料庫讀寫分離

    &lt;bean id="writeDataSource" class="org.logicalcobwebs.proxool.ProxoolDataSource"&gt;  

    &lt;property name="alias" value="writeDataSource"/&gt;  

    &lt;property name="driver" value="${write.connection.driver_class}" /&gt;  

    &lt;property name="driverUrl" value="${write.connection.url}" /&gt;  

    &lt;property name="user" value="${write.connection.username}" /&gt;  

    &lt;property name="password" value="${write.connection.password}" /&gt;  

    &lt;property name="maximumConnectionCount" value="${write.proxool.maximum.connection.count}"/&gt;  

    &lt;property name="minimumConnectionCount" value="${write.proxool.minimum.connection.count}" /&gt;  

    &lt;property name="statistics" value="${write.proxool.statistics}" /&gt;  

    &lt;property name="simultaneousBuildThrottle" value="${write.proxool.simultaneous.build.throttle}"/&gt;  

&lt;/bean&gt;  

1.2、讀庫配置

在應用層通過spring特性解決資料庫讀寫分離

&lt;bean id="readDataSource1" class="org.logicalcobwebs.proxool.ProxoolDataSource"&gt;  

    &lt;property name="alias" value="readDataSource"/&gt;  

    &lt;property name="driver" value="${read.connection.driver_class}" /&gt;  

    &lt;property name="driverUrl" value="${read.connection.url}" /&gt;  

    &lt;property name="user" value="${read.connection.username}" /&gt;  

    &lt;property name="password" value="${read.connection.password}" /&gt;  

    &lt;property name="maximumConnectionCount" value="${read.proxool.maximum.connection.count}"/&gt;  

    &lt;property name="minimumConnectionCount" value="${read.proxool.minimum.connection.count}" /&gt;  

    &lt;property name="statistics" value="${read.proxool.statistics}" /&gt;  

    &lt;property name="simultaneousBuildThrottle" value="${read.proxool.simultaneous.build.throttle}"/&gt;  

&lt;/bean&gt;   

1.3、讀寫動态庫配置   

通過writeDataSource指定寫庫,通過readDataSourceMap指定從庫清單,從庫清單預設通過順序輪詢來使用讀庫,具體參考javadoc;

在應用層通過spring特性解決資料庫讀寫分離

&lt;bean id="readWriteDataSource" class="cn.javass.common.datasource.ReadWriteDataSource"&gt;  

    &lt;property name="writeDataSource" ref="writeDataSource"/&gt;  

    &lt;property name="readDataSourceMap"&gt;  

       &lt;map&gt;  

          &lt;entry key="readDataSource1" value-ref="readDataSource1"/&gt;  

          &lt;entry key="readDataSource2" value-ref="readDataSource1"/&gt;  

          &lt;entry key="readDataSource3" value-ref="readDataSource1"/&gt;  

          &lt;entry key="readDataSource4" value-ref="readDataSource1"/&gt;  

       &lt;/map&gt;  

    &lt;/property&gt;  

2、XML事務屬性配置

是以讀方法必須是read-only(必須,以此來判斷是否是讀方法)。

在應用層通過spring特性解決資料庫讀寫分離

&lt;tx:advice id="txAdvice" transaction-manager="txManager"&gt;  

    &lt;tx:attributes&gt;  

        &lt;tx:method name="save*" propagation="REQUIRED" /&gt;  

        &lt;tx:method name="add*" propagation="REQUIRED" /&gt;  

        &lt;tx:method name="create*" propagation="REQUIRED" /&gt;  

        &lt;tx:method name="insert*" propagation="REQUIRED" /&gt;  

        &lt;tx:method name="update*" propagation="REQUIRED" /&gt;  

        &lt;tx:method name="merge*" propagation="REQUIRED" /&gt;  

        &lt;tx:method name="del*" propagation="REQUIRED" /&gt;  

        &lt;tx:method name="remove*" propagation="REQUIRED" /&gt;  

        &lt;tx:method name="put*" read-only="true"/&gt;  

        &lt;tx:method name="query*" read-only="true"/&gt;  

        &lt;tx:method name="use*" read-only="true"/&gt;  

        &lt;tx:method name="get*" read-only="true" /&gt;  

        &lt;tx:method name="count*" read-only="true" /&gt;  

        &lt;tx:method name="find*" read-only="true" /&gt;  

        &lt;tx:method name="list*" read-only="true" /&gt;  

        &lt;tx:method name="*" propagation="REQUIRED"/&gt;  

    &lt;/tx:attributes&gt;  

&lt;/tx:advice&gt;   

3、事務管理器

事務管理器管理的是readWriteDataSource

在應用層通過spring特性解決資料庫讀寫分離

&lt;bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"&gt;  

        &lt;property name="dataSource" ref="readWriteDataSource"/&gt;  

    &lt;/bean&gt;   

4、讀/寫動态資料庫選擇處理器

根據之前的txAdvice配置的事務屬性決定是讀/寫,具體參考javadoc;

forceChoiceReadWhenWrite:用于确定在如果目前是寫(即開啟了事務),下一步如果是讀,是直接參與到寫庫進行讀,還是強制從讀庫讀,具體參考javadoc;

在應用層通過spring特性解決資料庫讀寫分離

&lt;bean id="readWriteDataSourceTransactionProcessor" class="cn.javass.common.datasource.ReadWriteDataSourceProcessor"&gt;  

   &lt;property name="forceChoiceReadWhenWrite" value="false"/&gt;  

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

在應用層通過spring特性解決資料庫讀寫分離

&lt;aop:config expose-proxy="true"&gt;  

    &lt;!-- 隻對業務邏輯層實施事務 --&gt;  

    &lt;aop:pointcut id="txPointcut" expression="execution(* cn.javass..service..*.*(..))" /&gt;  

    &lt;aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/&gt;  

    &lt;!-- 通過AOP切面實作讀/寫庫選擇 --&gt;  

    &lt;aop:aspect order="-2147483648" ref="readWriteDataSourceTransactionProcessor"&gt;  

       &lt;aop:around pointcut-ref="txPointcut" method="determineReadOrWriteDB"/&gt;  

    &lt;/aop:aspect&gt;  

&lt;/aop:config&gt;   

1、事務切面一般橫切業務邏輯層;

2、此處我們使用readWriteDataSourceTransactionProcessor的通過AOP切面實作讀/寫庫選擇功能,order=Integer.MIN_VALUE(即最高的優先級),進而保證在操作事務之前已經決定了使用讀/寫庫。

6、測試用例

隻要配置好事務屬性(通過read-only=true指定讀方法)即可,其他選擇讀/寫庫的操作都交給readWriteDataSourceTransactionProcessor完成。

可以參考附件的:

cn.javass.readwrite.ReadWriteDBTestWithForceChoiceReadOnWriteFalse

cn.javass.readwrite.ReadWriteDBTestWithNoForceChoiceReadOnWriteTrue

可以下載下傳附件的代碼進行測試,具體選擇主/從可以參考日志輸出。

暫不想支援@Transactional注解式事務。

PS:歡迎拍磚指正。   

下載下傳次數: 692