在应用层通过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/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>
<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:欢迎拍砖指正。
在应用层通过spring特性解决数据库读写分离的更多相关文章
- Spring+MyBatis实现数据库读写分离方案
推荐第四种:https://github.com/shawntime/shawn-rwdb 方案1 通过MyBatis配置文件创建读写分离两个DataSource,每个SqlSessionFactor ...
- 使用Spring AOP切面解决数据库读写分离
http://blog.jobbole.com/103496/ 为了减轻数据库的压力,一般会使用数据库主从(master/slave)的方式,但是这种方式会给应用程序带来一定的麻烦,比如说,应用程序如 ...
- Spring AOP 实现数据库读写分离
背景 我们一般应用对数据库而言都是"读多写少",也就说对数据库读取数据的压力比较大,有一个思路就是说采用数据库集群的方案, 其中一个是主库,负责写入数据,我们称之为:写库: 其它都 ...
- Spring boot实现数据库读写分离
背景 数据库配置主从之后,如何在代码层面实现读写分离? 用户自定义设置数据库路由 Spring boot提供了AbstractRoutingDataSource根据用户定义的规则选择当前的数据库,这样 ...
- mysql+spring+mybatis实现数据库读写分离[代码配置] .
场景:一个读数据源一个读写数据源. 原理:借助spring的[org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource] ...
- 161220、使用Spring AOP实现MySQL数据库读写分离案例分析
一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量. 在进行数据库读写分离的时候,我们首先要进行数据库 ...
- spring+mybatis利用interceptor(plugin)兑现数据库读写分离
使用spring的动态路由实现数据库负载均衡 系统中存在的多台服务器是"地位相当"的,不过,同一时间他们都处于活动(Active)状态,处于负载均衡等因素考虑,数据访问请求需要在这 ...
- 170301、使用Spring AOP实现MySQL数据库读写分离案例分析
使用Spring AOP实现MySQL数据库读写分离案例分析 原创 2016-12-29 徐刘根 Java后端技术 一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案 ...
- 使用Spring AOP实现MySQL数据库读写分离案例分析
一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量. 在进行数据库读写分离的时候,我们首先要进行数据库 ...
随机推荐
- scrapy 动态网页处理——爬取鼠绘海贼王最新漫画
简介 scrapy是基于python的爬虫框架,易于学习与使用.本篇文章主要介绍如何使用scrapy爬取鼠绘漫画网海贼王最新一集的漫画. 源码参见:https://github.com/liudaol ...
- 初级模拟电路:1-2 PN结与二极管
回到目录 1. 掺杂半导体 上面我们分析了本征半导体的导电情况,但由于本征半导体的导电能力很低,没什么太大用处.所以,一般我们会对本征半导体材料进行掺杂,即使只添加了千分之一的杂质,也足以改变半导 ...
- jmeter的组成
1.jmeter的组成 线程组:控制多线程并发 断言:预期是否与实际结果一致,性能测试经验不建议做断言 定时器:自已操作线程停顿的时间 监听器:调试自已的脚本和对系统的监控 配置原件:配置的插件 后置 ...
- python中的全局变量、局部变量、实例变量
1.全局变量:在模块内,在所有函数.类外面. 2.局部变量:在函数内,在类方法内(未加self修饰的) 3.静态变量:在类内,但不在类方法内.[共同类所有,值改变后,之后所有的实例对象也改变] 4.实 ...
- 【LeetCode算法】LeetCode初级算法——字符串
在LeetCode初级算法的字符串专题中,共给出了九道题目,分别为:反转字符串,整数反转,字符串中的第一个唯一字符,有效的字母异位词,验证回文字符串,字符串转换整数,实现strStr(),报数,最 ...
- buddyinfo 内存碎片数据采集
不说了,上工具 #cat buddyinfo.sh #!/bin/sh #*************************************************************** ...
- css3 3d 魔方
<style><!--@charset "UTF-8"; * { margin: 0; padding: 0 } html,body{ width: 100%; ...
- [luogu P1962] 斐波那契数列(带快速幂矩阵乘法模板)
题目背景 大家都知道,斐波那契数列是满足如下性质的一个数列: • f(1) = 1 • f(2) = 1 • f(n) = f(n-1) + f(n-2) (n ≥ 2 且 n 为整数) 题目描述 请 ...
- Spring框架的理解
Spring 是一個开源的IOC和AOP容器框架! 具体描述为: 1.轻量级:Spring是非侵入性-基于Spring开发的应用中的对象可以不依赖API开发 2.依赖注入(DI---------dep ...
- 最快理解 - IO多路复用:select / poll / epoll 的区别.
目录 第一个解决方案(多线程) 第二个解决方案(select) 第三个解决方案(poll) 最终解决方案(epoll) 客栈遇到的问题 从开始学习编程后,我就想开一个 Hello World 餐厅,由 ...