使用Spring实现MySQL读写分离(转)
使用Spring实现MySQL读写分离
- 为什么要进行读写分离
大量的JavaWeb应用做的是IO密集型任务, 数据库的压力较大, 需要分流
大量的应用场景, 是读多写少, 数据库读取的压力更大
一个很自然的思路是使用一主多从的数据库集群: 一个是主库,负责写入数据;其它都是从库,负责读取数据. 主从库数据同步.
mysql原生支持主从复制

mysql主(称master)从(称slave)复制的原理:
1、master将数据改变记录到二进制日志(bin log)中, 这些记录叫binary log events
2、slave将master的binary log events拷贝到它的中继日志(relay log)
3、slave重做中继日志中的事件, 将改变反映它自己的数据(数据重演)
解决读写分离的方案大致有两种:
1)在应用层给读/写分别指定数据库
好处是数据源切换方便, 不用引入其他组件. 但是不能动态添加数据源.
2)使用中间件解决
好处是源程序不需要做任何改动, 还可以动态添加数据源. 但中间件会带来一定的性能损失.
目前有mysql-proxy, mycat, altas等
- MySQL主从配置
主库配置
修改my.ini:
开启主从复制,主库的配置
log-bin = mysql3306-bin
指定主库serverid
server-id=101
指定同步的数据库,如果不指定则同步全部数据库
binlog-do-db=mydb
执行以下SQL:
SHOW MASTER STATUS;

记录下Position值,需要在从库中设置同步起始值。
授权从库用户slave01使用123456密码登录本库
grant replication slave on . to 'slave01'@'127.0.0.1' identified by '123456';
flush privileges;
从库配置
修改my.ini:
指定serverid
server-id=102
执行以下SQL:
CHANGE MASTER TO
master_host='127.0.0.1',
master_user='slave01',
master_password='123456',
master_port=3306,
master_log_file='mysql3306-bin.000006', #设置主库时记下的Position
master_log_pos=1120;
启动slave同步
START SLAVE;
查看同步状态 Slave_IO_Running和Slave_SQL_Running都为Yes说明同步成功
SHOW SLAVE STATUS;
- Spring动态数据源+AOP实现读写分离
这里采用的是应用层的读写分离方案
使用AOP, 在执行Service方法前判断,是使用写库还是读库
可以根据方法名作为依据判断,比如说以query、find、get等开头的就走读库,其他的走写库
切面类:
复制代码
/**
* 如果在spring配置了事务的策略,则标记了ReadOnly的方法用从库Slave, 其它使用主库Master。
* 如果没有配置事务策略, 则采用方法名匹配, 以query、find、get开头的方法用Slave,其它用Master。
*/
public class DataSourceAspect {
private List<String> slaveMethodPattern = new ArrayList<String>(); //保存有readonly属性的带通配符方法名
private static final String[] defaultSlaveMethodStartWith = new String[]{"query", "find", "get" };
private String[] slaveMethodStartWith; //保存有slaveMethodStartWith属性的方法名头部
//注入
public void setTxAdvice(TransactionInterceptor txAdvice) throws Exception {
if (txAdvice == null) {
// 没有配置事务策略
return;
}
//从txAdvice获取策略配置信息
TransactionAttributeSource transactionAttributeSource = txAdvice.getTransactionAttributeSource();
if (!(transactionAttributeSource instanceof NameMatchTransactionAttributeSource)) {
return;
}
//使用反射技术获取到NameMatchTransactionAttributeSource对象中的nameMap属性值
NameMatchTransactionAttributeSource matchTransactionAttributeSource = (NameMatchTransactionAttributeSource) transactionAttributeSource;
Field nameMapField = ReflectionUtils.findField(NameMatchTransactionAttributeSource.class, "nameMap");
nameMapField.setAccessible(true); //设置该字段可访问
//获取nameMap的值
Map<String, TransactionAttribute> map = (Map<String, TransactionAttribute>) nameMapField.get(matchTransactionAttributeSource);
//遍历nameMap
for (Map.Entry<String, TransactionAttribute> entry : map.entrySet()) {
if (!entry.getValue().isReadOnly()) { // 定义了ReadOnly的策略才加入到slaveMethodPattern
continue;
}
slaveMethodPattern.add(entry.getKey());
}
}
// 切面 before方法
public void before(JoinPoint point) {
// 获取到当前执行的方法名
String methodName = point.getSignature().getName();
boolean isSlave = false;
if (slaveMethodPattern.isEmpty()) {
// 没有配置read-only属性,采用方法名匹配方式
isSlave = isSlaveByMethodName(methodName);
} else {
// 配置read-only属性, 采用通配符匹配
for (String mappedName : slaveMethodPattern) {
if (isSlaveByConfigWildcard(methodName, mappedName)) {
isSlave = true;
break;
}
}
}
if (isSlave) {
// 标记为读库
DynamicDataSource.markMaster(true);
} else {
// 标记为写库
DynamicDataSource.markMaster(false);
}
}
// 匹配以指定名称开头的方法名, 配置了slaveMethodStartWith属性, 或使用默认
private Boolean isSlaveByMethodName(String methodName) {
return StringUtils.startsWithAny(methodName, getSlaveMethodStartWith());
}
// 匹配带通配符"xxx*", "*xxx" 和 "*xxx*"的方法名, 源自配置了readonly属性的方法名
protected boolean isSlaveByConfigWildcard(String methodName, String mappedName) {
return PatternMatchUtils.simpleMatch(mappedName, methodName);
}
// 注入
public void setSlaveMethodStartWith(String[] slaveMethodStartWith) {
this.slaveMethodStartWith = slaveMethodStartWith;
}
public String[] getSlaveMethodStartWith() {
if(this.slaveMethodStartWith == null){
// 没有配置slaveMethodStartWith属性,使用默认
return defaultSlaveMethodStartWith;
}
return slaveMethodStartWith;
}
}
复制代码
Spring的RoutingDataSource
复制代码
/**
* 使用Spring的动态数据源,需要实现AbstractRoutingDataSource
* 通过determineCurrentLookupKey方法拿到识别key来判断选择读/写数据源
* token显然是多例的, 所以引入ThreadLocal保存
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
// 读库总数
private Integer slaveCount;
// 读库轮询计数, 初始为-1, 本类为单例, AtomicInteger线程安全
private AtomicInteger counter = new AtomicInteger(-1);
// 存储读库的识别key sl1ve01, slave02... 写库识别key为master
private List<Object> slaveDataSources = new ArrayList<Object>();
//当前线程的写库/读库token
private static final ThreadLocal<Boolean> tokenHolder = new ThreadLocal<>();
public static void markMaster(boolean isMaster){
tokenHolder.set(isMaster);
}
@Override
protected Object determineCurrentLookupKey() {
if (tokenHolder.get()) {
return "master"; // 写库
}
// 轮询读库, 得到的下标为:0、1、2...
Integer index = counter.incrementAndGet() % slaveCount;
if (counter.get() > 99999) { // 以免超出Integer范围
counter.set(-1);
}
return slaveDataSources.get(index);
}
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
// 父类的resolvedDataSources属性是private, 需要使用反射获取
Field field = ReflectionUtils.findField(AbstractRoutingDataSource.class, "resolvedDataSources");
field.setAccessible(true); // 设置可访问
try {
Map<Object, DataSource> resolvedDataSources = (Map<Object, DataSource>) field.get(this);
// 读库数等于dataSource总数减写库数
this.slaveCount = resolvedDataSources.size() - 1;
for (Map.Entry<Object, DataSource> entry : resolvedDataSources.entrySet()) {
if ("master".equals(entry.getKey())) {
continue;
}
slaveDataSources.add(entry.getKey());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
复制代码
spring配置文件
复制代码
<!-- 定义事务策略 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--所有以query开头的方法都是只读的 -->
<tx:method name="query*" read-only="true" /> <!-- readonly属性 -->
<!--其他方法使用默认事务策略 -->
<tx:method name="*" />
</tx:attributes>
</tx:advice>
<!-- 定义AOP切面处理器 -->
<bean class="com.zx.DataSourceAspect" id="dataSourceAspect">
<!-- 注入事务策略 -->
<property name="txAdvice" ref="txAdvice"/>
<!-- 指定slave方法的前缀(非必须) -->
<property name="slaveMethodStartWith" value="query,find,get"/>
</bean>
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(* com.zx.service.*.*(..))" />
<!-- 将切面应用到自定义的切面处理器上,-9999保证该切面优先级最高执行 -->
<aop:aspect ref="dataSourceAspect" order="-9999">
<aop:before method="before" pointcut-ref="myPointcut" />
</aop:aspect>
</aop:config>
<!-- 定义数据源,继承了spring的动态数据源 -->
<bean id="dataSource" class="com.zx.DynamicDataSource">
<!-- 设置多个数据源 -->
<property name="targetDataSources">
<map key-type="java.lang.String">
<!-- 这些设置的key和determineCurrentLookupKey方法拿到的key相比对, 根据匹配选择数据源 -->
<entry key="master" value-ref="masterDataSource"/> <!-- value-ref指向数据源 -->
<entry key="slave01" value-ref="slave01DataSource"/>
<entry key="slave02" value-ref="slave02DataSource"/>
<entry key="slave03" value-ref="slave03DataSource"/>
</map>
</property>
<!-- 设置默认的数据源,这里默认走写库 -->
<property name="defaultTargetDataSource" ref="masterDataSource"/>
</bean>
复制代码
使用Spring实现MySQL读写分离(转)的更多相关文章
- 使用Spring实现MySQL读写分离
1. 为什么要进行读写分离 大量的JavaWeb应用做的是IO密集型任务, 数据库的压力较大, 需要分流 大量的应用场景, 是读多写少, 数据库读取的压力更大 一个很自然的思路是使用一主多从的数据库集 ...
- Mybatis+Spring实现Mysql读写分离
使用spring AbstractRoutingDatasource实现多数据源 public class DynamicDataSource extends AbstractRoutingDataS ...
- spring+mybatis实现读写分离
springmore-core spring+ibatis实现读写分离 特点 无缝结合spring+ibatis,对于程序员来说,是透明的 除了修改配置信息之外,程序的代码不需要修改任何东西 支持sp ...
- Spring AOP 实现读写分离
原文地址:Spring AOP 实现读写分离 博客地址:http://www.extlight.com 一.前言 上一篇<MySQL 实现主从复制> 文章中介绍了 MySQL 主从复制的搭 ...
- 提高性能,MySQL 读写分离环境搭建
这是松哥之前一个零散的笔记,整理出来分享给大伙! MySQL 读写分离在互联网项目中应该算是一个非常常见的需求了.受困于 Linux 和 MySQL 版本问题,很多人经常会搭建失败,今天松哥就给大伙举 ...
- 提高性能,MySQL 读写分离环境搭建(一)
这是松哥之前一个零散的笔记,整理出来分享给大伙! MySQL 读写分离在互联网项目中应该算是一个非常常见的需求了.受困于 Linux 和 MySQL 版本问题,很多人经常会搭建失败,今天松哥就给大伙举 ...
- SSM/SSH框架的MySQL 读写分离实现的一种简单方法
简介 MySQL已经是使用最为广泛的一种数据库,往往实际使用过程中,为实现高可用及高性能,项目会采用主丛复制的方式实现读写分离.MySQL本身支持复制,通过简单的配置即可实现一主多从的配置,具体实现可 ...
- mysql读写分离(PHP类)
mysql读写分离(PHP类) 博客分类: php mysql 自己实现了php的读写分离,并且不用修改程序 优点:实现了读写分离,不依赖服务器硬件配置,并且都是可以配置read服务器,无限扩展 ...
- amoeba实现MySQL读写分离
amoeba实现MySQL读写分离 准备环境:主机A和主机B作主从配置,IP地址为192.168.131.129和192.168.131.130,主机C作为中间件,也就是作为代理服务器,IP地址为19 ...
随机推荐
- Git 内部原理 - (3) Git 引用 (4)包文件
Git 引用 我们可以借助类似于 git log 1a410e 这样的命令来浏览完整的提交历史,但为了能遍历那段历史从而找到所有相关对象,你仍须记住 1a410e 是最后一个提交. 我们需要一个文件来 ...
- Perl模块利用CPAN在线安装自动化
需要解决2个问题: 1. 如何与CPAN交互:利用perl –MCPAN –e ‘install 模块’ 2. 如何安装指定的版本:作者/模块-版本.tar.gz How to install a ...
- Unity Shader (四)片段程序示例
1.环境光+漫反射+高光+点光源 Shader "Custom/Example_Frag_1" { properties { _MainColor(,,,) _Specular ...
- Java基础学习总结(18)——网络编程
一.网络基础概念 首先理清一个概念:网络编程 != 网站编程,网络编程现在一般称为TCP/IP编程. 二.网络通信协议及接口 三.通信协议分层思想 四.参考模型 五.IP协议 每个人的电脑都有一个独一 ...
- ArcGIS 空间查询
public static bool QueryMessPoint(IActiveView activeView, IFeatureClass featureClass, string whereCl ...
- 最简单的HTML5游戏——贪吃蛇
<html> <head> <meta charset="UTF-8"/> <title>贪吃蛇</title> < ...
- 智课雅思短语---一、be no exception
智课雅思短语---一.be no exception 一.总结 一句话总结:…也不例外? …be no exception 1.经济的快速发展? the rapid development of ec ...
- CSS 文本字体颜色设置方法(CSS color)
CSS 文本字体颜色设置方法(CSS color) 一.认识CSS 颜色(CSS color) 这里要介绍的是网页设置颜色包含有哪些:网页颜色规定规范. 1.常用颜色地方包含:字体颜色.超链接颜色.网 ...
- 从 dig(nslookup) bind —— windows 下的域名解析服务器信息的查看
dig(domain information groper,之所以选择这三个词,在于这三个词的首字母构成的词 dig 也有探索挖掘的含义)本身是 Linux 下的查询 DNS 信息的工具,功能类似 n ...
- ES6学习笔记(八)第七种类型Symbol
1.概述 ES5 的对象属性名都是字符串,这容易造成属性名的冲突.比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突.如果有一种 ...