?
如何配置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/188598 和 http://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 、寫庫配置
<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> </bean>?
?
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"/> </bean>?
?
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 :歡迎拍磚指正。 ???
?
?
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
