如何配置mysql數據庫的主從?
單機配置mysql主從:http://my.oschina.net/god/blog/496javascript
常見的解決數據庫讀寫分離有兩種方案
1、應用層java
http://neoremind.net/2011/06/spring實現數據庫讀寫分離mysql
目前的一些解決方案須要在程序中手動指定數據源,比較麻煩,後邊我會經過AOP思想來解決這個問題。web
2、中間件spring
mysql-proxy:http://hi.baidu.com/geshuai2008/item/0ded5389c685645f850fab07sql
Amoeba for MySQL:http://www.iteye.com/topic/188598和http://www.iteye.com/topic/1113437數據庫
此處咱們介紹一種在應用層的解決方案,經過spring動態數據源和AOP來解決數據庫的讀寫分離。express
該方案目前已經在一個互聯網項目中使用了,並且能夠很好的工做。app
該方案目前支持
一讀多寫;當寫時默認讀操做到寫庫、當寫時強制讀操做到讀庫。負載均衡
考慮將來支持
讀庫負載均衡、讀庫故障轉移等。
使用場景
不想引入中間件,想在應用層解決讀寫分離,能夠考慮這個方案;
建議數據訪問層使用jdbc、ibatis,不建議hibernate。
優點
應用層解決,不引入額外中間件;
在應用層支持『當寫時默認讀操做到寫庫』,這樣若是咱們採用這種方案,在寫操做後讀數據直接從寫庫拿,不會產生數據複製的延遲問題;
應用層解決讀寫分離,理論支持任意數據庫。
缺點
一、不支持@Transactional註解事務,此方案要求全部讀方法必須是read-only=true,所以若是是@Transactional,這樣就要求在每個讀方法頭上加@Transactional 且readOnly屬性=true,至關麻煩。 :oops:
二、必須按照配置約定進行配置,不夠靈活。
兩種方案
![](http://static.javashuo.com/static/loading.gif)
方案1:當只有讀操做的時候,直接操做讀庫(從庫);
當在寫事務(即寫主庫)中讀時,也是讀主庫(即參與到主庫操做),這樣的優點是能夠防止寫完後可能讀不到剛纔寫的數據;
此方案實際上是使用事務傳播行爲爲:SUPPORTS解決的。
![](http://static.javashuo.com/static/loading.gif)
方案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>
<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>
<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>
<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>
<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>
<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>
<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>
<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:歡迎拍磚指正。