在应用层通过spring特性解决数据库读写分离

系统 1626 0

 

如何配置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、必须按照配置约定进行配置,不够灵活。

 

两种方案

在应用层通过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 、写库配置

      	<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 :欢迎拍砖指正。    

 

 

 

在应用层通过spring特性解决数据库读写分离


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论