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、必須按照配置約定進行配置,不夠靈活。

方案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、寫庫配置
<bean id="writeDataSource" class="org.logicalcobwebs.proxool.ProxoolDataSource">
<property name="alias" value="writeDataSource"/>
<property name="driver" value="${write.connection.driver_class}" />
<property name="driverUrl" value="${write.connection.url}" />
<property name="user" value="${write.connection.username}" />
<property name="password" value="${write.connection.password}" />
<property name="maximumConnectionCount" value="${write.proxool.maximum.connection.count}"/>
<property name="minimumConnectionCount" value="${write.proxool.minimum.connection.count}" />
<property name="statistics" value="${write.proxool.statistics}" />
<property name="simultaneousBuildThrottle" value="${write.proxool.simultaneous.build.throttle}"/>
</bean>
1.2、讀庫配置
<bean id="readDataSource1" class="org.logicalcobwebs.proxool.ProxoolDataSource">
<property name="alias" value="readDataSource"/>
<property name="driver" value="${read.connection.driver_class}" />
<property name="driverUrl" value="${read.connection.url}" />
<property name="user" value="${read.connection.username}" />
<property name="password" value="${read.connection.password}" />
<property name="maximumConnectionCount" value="${read.proxool.maximum.connection.count}"/>
<property name="minimumConnectionCount" value="${read.proxool.minimum.connection.count}" />
<property name="statistics" value="${read.proxool.statistics}" />
<property name="simultaneousBuildThrottle" value="${read.proxool.simultaneous.build.throttle}"/>
</bean>
1.3、讀寫動态庫配置
通過writeDataSource指定寫庫,通過readDataSourceMap指定從庫清單,從庫清單預設通過順序輪詢來使用讀庫,具體參考javadoc;
<bean id="readWriteDataSource" class="cn.javass.common.datasource.ReadWriteDataSource">
<property name="writeDataSource" ref="writeDataSource"/>
<property name="readDataSourceMap">
<map>
<entry key="readDataSource1" value-ref="readDataSource1"/>
<entry key="readDataSource2" value-ref="readDataSource1"/>
<entry key="readDataSource3" value-ref="readDataSource1"/>
<entry key="readDataSource4" value-ref="readDataSource1"/>
</map>
</property>
2、XML事務屬性配置
是以讀方法必須是read-only(必須,以此來判斷是否是讀方法)。
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="create*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="merge*" propagation="REQUIRED" />
<tx:method name="del*" propagation="REQUIRED" />
<tx:method name="remove*" propagation="REQUIRED" />
<tx:method name="put*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="use*" read-only="true"/>
<tx:method name="get*" read-only="true" />
<tx:method name="count*" read-only="true" />
<tx:method name="find*" read-only="true" />
<tx:method name="list*" read-only="true" />
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
3、事務管理器
事務管理器管理的是readWriteDataSource
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="readWriteDataSource"/>
</bean>
4、讀/寫動态資料庫選擇處理器
根據之前的txAdvice配置的事務屬性決定是讀/寫,具體參考javadoc;
forceChoiceReadWhenWrite:用于确定在如果目前是寫(即開啟了事務),下一步如果是讀,是直接參與到寫庫進行讀,還是強制從讀庫讀,具體參考javadoc;
<bean id="readWriteDataSourceTransactionProcessor" class="cn.javass.common.datasource.ReadWriteDataSourceProcessor">
<property name="forceChoiceReadWhenWrite" value="false"/>
5、事務切面和讀/寫庫選擇切面
<aop:config expose-proxy="true">
<!-- 隻對業務邏輯層實施事務 -->
<aop:pointcut id="txPointcut" expression="execution(* cn.javass..service..*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
<!-- 通過AOP切面實作讀/寫庫選擇 -->
<aop:aspect order="-2147483648" ref="readWriteDataSourceTransactionProcessor">
<aop:around pointcut-ref="txPointcut" method="determineReadOrWriteDB"/>
</aop:aspect>
</aop:config>
1、事務切面一般橫切業務邏輯層;
2、此處我們使用readWriteDataSourceTransactionProcessor的通過AOP切面實作讀/寫庫選擇功能,order=Integer.MIN_VALUE(即最高的優先級),進而保證在操作事務之前已經決定了使用讀/寫庫。
6、測試用例
隻要配置好事務屬性(通過read-only=true指定讀方法)即可,其他選擇讀/寫庫的操作都交給readWriteDataSourceTransactionProcessor完成。
可以參考附件的:
cn.javass.readwrite.ReadWriteDBTestWithForceChoiceReadOnWriteFalse
cn.javass.readwrite.ReadWriteDBTestWithNoForceChoiceReadOnWriteTrue
可以下載下傳附件的代碼進行測試,具體選擇主/從可以參考日志輸出。
暫不想支援@Transactional注解式事務。
PS:歡迎拍磚指正。
下載下傳次數: 692