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

如何配置mysql數據庫的主從?

單機配置mysql主從:http://my.oschina.net/god/blog/496

 

常見的解決數據庫讀寫分離有兩種方案

1、應用層

http://neoremind.net/2011/06/spring實現數據庫讀寫分離

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

 

2、中間件

mysql-proxy:http://hi.baidu.com/geshuai2008/item/0ded5389c685645f850fab07

Amoeba for MySQL:http://www.iteye.com/topic/188598http://www.iteye.com/topic/1113437

 

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

 

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

 

該方案目前支持

一讀多寫;當寫時默認讀操作到寫庫、當寫時強制讀操作到讀庫。

 

考慮未來支持

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

 

使用場景

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

建議數據訪問層使用jdbc、ibatis,不建議hibernate。

 

優勢

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

在應用層支持『當寫時默認讀操作到寫庫』,這樣如果我們採用這種方案,在寫操作後讀數據直接從寫庫拿,不會產生數據複製的延遲問題;

應用層解決讀寫分離,理論支持任意數據庫。

 

 

缺點

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

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

 

兩種方案



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

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

 

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

 


方案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、寫庫配置

Java代碼  收藏代碼
  1.     <bean id="writeDataSource" class="org.logicalcobwebs.proxool.ProxoolDataSource">  
  2.     <property name="alias" value="writeDataSource"/>  
  3.     <property name="driver" value="${write.connection.driver_class}" />  
  4.     <property name="driverUrl" value="${write.connection.url}" />  
  5.     <property name="user" value="${write.connection.username}" />  
  6.     <property name="password" value="${write.connection.password}" />  
  7.     <property name="maximumConnectionCount" value="${write.proxool.maximum.connection.count}"/>  
  8.     <property name="minimumConnectionCount" value="${write.proxool.minimum.connection.count}" />  
  9.     <property name="statistics" value="${write.proxool.statistics}" />  
  10.     <property name="simultaneousBuildThrottle" value="${write.proxool.simultaneous.build.throttle}"/>  
  11. </bean>  

 

1.2、讀庫配置

Java代碼  收藏代碼
  1. <bean id="readDataSource1" class="org.logicalcobwebs.proxool.ProxoolDataSource">  
  2.     <property name="alias" value="readDataSource"/>  
  3.     <property name="driver" value="${read.connection.driver_class}" />  
  4.     <property name="driverUrl" value="${read.connection.url}" />  
  5.     <property name="user" value="${read.connection.username}" />  
  6.     <property name="password" value="${read.connection.password}" />  
  7.     <property name="maximumConnectionCount" value="${read.proxool.maximum.connection.count}"/>  
  8.     <property name="minimumConnectionCount" value="${read.proxool.minimum.connection.count}" />  
  9.     <property name="statistics" value="${read.proxool.statistics}" />  
  10.     <property name="simultaneousBuildThrottle" value="${read.proxool.simultaneous.build.throttle}"/>  
  11. </bean>   

1.3、讀寫動態庫配置   

通過writeDataSource指定寫庫,通過readDataSourceMap指定從庫列表,從庫列表默認通過順序輪詢來使用讀庫,具體參考javadoc

Java代碼  收藏代碼
  1. <bean id="readWriteDataSource" class="cn.javass.common.datasource.ReadWriteDataSource">  
  2.     <property name="writeDataSource" ref="writeDataSource"/>  
  3.     <property name="readDataSourceMap">  
  4.        <map>  
  5.           <entry key="readDataSource1" value-ref="readDataSource1"/>  
  6.           <entry key="readDataSource2" value-ref="readDataSource1"/>  
  7.           <entry key="readDataSource3" value-ref="readDataSource1"/>  
  8.           <entry key="readDataSource4" value-ref="readDataSource1"/>  
  9.        </map>  
  10.     </property>  
  11. </bean>   

 

2XML事務屬性配置

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

Java代碼  收藏代碼
  1. <tx:advice id="txAdvice" transaction-manager="txManager">  
  2.     <tx:attributes>  
  3.         <tx:method name="save*" propagation="REQUIRED" />  
  4.         <tx:method name="add*" propagation="REQUIRED" />  
  5.         <tx:method name="create*" propagation="REQUIRED" />  
  6.         <tx:method name="insert*" propagation="REQUIRED" />  
  7.         <tx:method name="update*" propagation="REQUIRED" />  
  8.         <tx:method name="merge*" propagation="REQUIRED" />  
  9.         <tx:method name="del*" propagation="REQUIRED" />  
  10.         <tx:method name="remove*" propagation="REQUIRED" />  
  11.           
  12.         <tx:method name="put*" read-only="true"/>  
  13.         <tx:method name="query*" read-only="true"/>  
  14.         <tx:method name="use*" read-only="true"/>  
  15.         <tx:method name="get*" read-only="true" />  
  16.         <tx:method name="count*" read-only="true" />  
  17.         <tx:method name="find*" read-only="true" />  
  18.         <tx:method name="list*" read-only="true" />  
  19.           
  20.         <tx:method name="*" propagation="REQUIRED"/>  
  21.     </tx:attributes>  
  22. </tx:advice>   

 

3、事務管理器

事務管理器管理的是readWriteDataSource

Java代碼  收藏代碼
  1. <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
  2.         <property name="dataSource" ref="readWriteDataSource"/>  
  3.     </bean>   

 

4、讀/寫動態數據庫選擇處理器

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

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

Java代碼  收藏代碼
  1. <bean id="readWriteDataSourceTransactionProcessor" class="cn.javass.common.datasource.ReadWriteDataSourceProcessor">  
  2.    <property name="forceChoiceReadWhenWrite" value="false"/>  
  3. </bean>   

 

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

Java代碼  收藏代碼
  1. <aop:config expose-proxy="true">  
  2.     <!-- 只對業務邏輯層實施事務 -->  
  3.     <aop:pointcut id="txPointcut" expression="execution(* cn.javass..service..*.*(..))" />  
  4.     <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>  
  5.       
  6.     <!-- 通過AOP切面實現讀/寫庫選擇 -->  
  7.     <aop:aspect order="-2147483648" ref="readWriteDataSourceTransactionProcessor">  
  8.        <aop:around pointcut-ref="txPointcut" method="determineReadOrWriteDB"/>  
  9.     </aop:aspect>  
  10. </aop:config>   

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

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

 

6、測試用例

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

 

可以參考附件的:

cn.javass.readwrite.ReadWriteDBTestWithForceChoiceReadOnWriteFalse

cn.javass.readwrite.ReadWriteDBTestWithNoForceChoiceReadOnWriteTrue

 

 

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章