一、前言

分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量。

在进行数据库读写分离的时候,我们首先要进行数据库的主从配置,最简单的是一台Master和一台Slave(大型网站系统的话,当然会很复杂,这里只是分析了最简单的情况)。通过主从配置主从数据库保持了相同的数据,我们在进行读操作的时候访问从数据库Slave,在进行写操作的时候访问主数据库Master。这样的话就减轻了一台服务器的压力。

在进行读写分离案例分析的时候。首先,配置数据库的主从复制,下边是两种方法(任选其一即可):

1、MySQL5.6 数据库主从(Master/Slave)同步安装与配置详解

2、使用mysqlreplicate命令快速搭建 Mysql 主从复制

当然,只是简单的为了看一下如何用代码的方式实现数据库的读写分离,完全不必要去配置主从数据库,只需要两台安装了 相同数据库的机器就可以了。

二、实现读写分离的两种方法

具体到开发中,实现读写分离常用的有两种方式:

1、第一种方式是我们最常用的方式,就是定义2个数据库连接,一个是MasterDataSource,另一个是SlaveDataSource。更新数据时我们读取MasterDataSource,查询数据时我们读取SlaveDataSource。这种方式很简单,我就不赘述了。

2、第二种方式动态数据源切换,就是在程序运行时,把数据源动态织入到程序中,从而选择读取主库还是从库。主要使用的技术是:Annotation,Spring AOP ,反射。

下面会详细的介绍实现方式。

三、Aop实现主从数据库的读写分离案例

1、项目代码地址

目前该Demo的项目地址在开源中国 码云 上边:http://git.oschina.net/xuliugen/aop-choose-db-demo

或者CSDN免费下载:

http://download.csdn.net/detail/u010870518/9724872

2、项目结构

上图中,除了标记的代码,其他的主要是配置代码和业务代码。

3、具体分析

该项目是SSM框架的一个demo,Spring、Spring MVC和MyBatis,具体的配置文件不在过多介绍。

(1)UserContoller模拟读写数据

/**
* Created by xuliugen on 2016/5/4.
*/
@Controller
@RequestMapping(value = "/user", produces = {"application/json;charset=UTF-8"})
public class UserController { @Inject
private IUserService userService; //http://localhost:8080/user/select.do
@ResponseBody
@RequestMapping(value = "/select.do", method = RequestMethod.GET)
public String select() {
User user = userService.selectUserById(123);
return user.toString();
} //http://localhost:8080/user/add.do
@ResponseBody
@RequestMapping(value = "/add.do", method = RequestMethod.GET)
public String add() {
boolean isOk = userService.addUser(new User("333", "444"));
return isOk == true ? "shibai" : "chenggong";
}
}

模拟读写数据,调用IUserService 。

(2)spring-db.xml读写数据源配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="statFilter" class="com.alibaba.druid.filter.stat.StatFilter" lazy-init="true">
<property name="logSlowSql" value="true"/>
<property name="mergeSql" value="true"/>
</bean> <!-- 数据库连接 -->
<bean id="readDataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close" init-method="init" lazy-init="true">
<property name="driverClassName" value="${driver}"/>
<property name="url" value="${url1}"/>
<property name="username" value="root"/>
<property name="password" value="${password}"/>
<!-- 省略部分内容 -->
</bean> <bean id="writeDataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close" init-method="init" lazy-init="true">
<property name="driverClassName" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="root"/>
<property name="password" value="${password}"/>
<!-- 省略部分内容 -->
</bean> <!-- 配置动态分配的读写 数据源 -->
<bean id="dataSource" class="com.xuliugen.choosedb.demo.aspect.ChooseDataSource" lazy-init="true">
<property name="targetDataSources">
<map key-type="java.lang.String" value-type="javax.sql.DataSource">
<!-- write -->
<entry key="write" value-ref="writeDataSource"/>
<!-- read -->
<entry key="read" value-ref="readDataSource"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="writeDataSource"/>
<property name="methodType">
<map key-type="java.lang.String">
<!-- read -->
<entry key="read" value=",get,select,count,list,query"/>
<!-- write -->
<entry key="write" value=",add,create,update,delete,remove,"/>
</map>
</property>
</bean> </beans>

上述配置中,配置了readDataSource和writeDataSource两个数据源,但是交给SqlSessionFactoryBean进行管理的只有dataSource,其中使用到了:com.xuliugen.choosedb.demo.aspect.ChooseDataSource 这个是进行数据库选择的。

<property name="methodType">
<map key-type="java.lang.String">
<!-- read -->
<entry key="read" value=",get,select,count,list,query"/>
<!-- write -->
<entry key="write" value=",add,create,update,delete,remove,"/>
</map>
</property>

配置了数据库具体的那些是读哪些是写的前缀关键字。ChooseDataSource的具体代码如下:

(3)ChooseDataSource

/**
* 获取数据源,用于动态切换数据源
*/
public class ChooseDataSource extends AbstractRoutingDataSource { public static Map<String, List<String>> METHOD_TYPE_MAP = new HashMap<String, List<String>>(); /**
* 实现父类中的抽象方法,获取数据源名称
* @return
*/
protected Object determineCurrentLookupKey() {
return DataSourceHandler.getDataSource();
} // 设置方法名前缀对应的数据源
public void setMethodType(Map<String, String> map) {
for (String key : map.keySet()) {
List<String> v = new ArrayList<String>();
String[] types = map.get(key).split(",");
for (String type : types) {
if (StringUtils.isNotBlank(type)) {
v.add(type);
}
}
METHOD_TYPE_MAP.put(key, v);
}
}
}

(4)DataSourceAspect进行具体方法的AOP拦截

/**
* 切换数据源(不同方法调用不同数据源)
*/
@Aspect
@Component
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DataSourceAspect { protected Logger logger = LoggerFactory.getLogger(this.getClass()); @Pointcut("execution(* com.xuliugen.choosedb.demo.mybatis.dao.*.*(..))")
public void aspect() {
} /**
* 配置前置通知,使用在方法aspect()上注册的切入点
*/
@Before("aspect()")
public void before(JoinPoint point) {
String className = point.getTarget().getClass().getName();
String method = point.getSignature().getName();
logger.info(className + "." + method + "(" + StringUtils.join(point.getArgs(), ",") + ")");
try {
for (String key : ChooseDataSource.METHOD_TYPE_MAP.keySet()) {
for (String type : ChooseDataSource.METHOD_TYPE_MAP.get(key)) {
if (method.startsWith(type)) {
DataSourceHandler.putDataSource(key);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

(5)DataSourceHandler,数据源的Handler类

package com.xuliugen.choosedb.demo.aspect;

/**
* 数据源的Handler类
*/
public class DataSourceHandler { // 数据源名称线程池
public static final ThreadLocal<String> holder = new ThreadLocal<String>(); /**
* 在项目启动的时候将配置的读、写数据源加到holder中
*/
public static void putDataSource(String datasource) {
holder.set(datasource);
} /**
* 从holer中获取数据源字符串
*/
public static String getDataSource() {
return holder.get();
}
}

主要代码,如上所述。

使用Spring AOP实现MySQL数据库读写分离案例分析的更多相关文章

  1. 170301、使用Spring AOP实现MySQL数据库读写分离案例分析

    使用Spring AOP实现MySQL数据库读写分离案例分析 原创 2016-12-29 徐刘根 Java后端技术 一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案 ...

  2. 161220、使用Spring AOP实现MySQL数据库读写分离案例分析

    一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解决方案,更是最大限度了提高了应用中读取 (Read)数据的速度和并发量. 在进行数据库读写分离的时候,我们首先要进行数据库 ...

  3. 使用Spring AOP实现MySql的读写分离

    转自:http://blog.csdn.net/xlgen157387/article/details/53930382 一.前言 分布式环境下数据库的读写分离策略是解决数据库读写性能瓶颈的一个关键解 ...

  4. 使用Spring AOP切面解决数据库读写分离

    http://blog.jobbole.com/103496/ 为了减轻数据库的压力,一般会使用数据库主从(master/slave)的方式,但是这种方式会给应用程序带来一定的麻烦,比如说,应用程序如 ...

  5. Spring AOP实现Mysql数据库主从切换(一主多从)

    设置数据库主从切换的原因:数据库中经常发生的是“读多写少”,这样读操作对数据库压力比较大,通过采用数据库集群方案, 一个数据库是主库,负责写:其他为从库,负责读,从而实现读写分离增大数据库的容错率.  ...

  6. MySQL数据库读写分离、读负载均衡方案选择

    MySQL数据库读写分离.读负载均衡方案选择 一.MySQL Cluster外键所关联的记录在别的分片节点中性能很差对需要进行分片的表需要修改引擎Innodb为NDB因此MySQL Cluster不适 ...

  7. Spring + Mybatis项目实现数据库读写分离

    主要思路:通过实现AbstractRoutingDataSource类来动态管理数据源,利用面向切面思维,每一次进入service方法前,选择数据源. 1.首先pom.xml中添加aspect依赖 & ...

  8. Linux安装Mycat1.6.7.4并实现Mysql数据库读写分离简单配置

    1. Mycat简介 一个彻底开源的,面向企业应用开发的大数据库集群 支持事务.ACID.可以替代MySQL的加强版数据库 一个可以视为MySQL集群的企业级数据库,用来替代昂贵的Oracle集群 一 ...

  9. spring结合mybatis实现数据库读写分离

    随着系统用户访问量的不断增加,数据库的频繁访问将成为我们系统的一大瓶颈之一.由于项目前期用户量不大,我们实现单一的数据库就能完成.但是后期单一的数据库根本无法支撑庞大的项目去访问数据库,那么如何解决这 ...

随机推荐

  1. Working With Push Buttons In Oracle Forms

    Managing push buttons at run time in Oracle Forms is very simple and in this tutorial you will learn ...

  2. 解决unknown import path "golang.org/x/sys/unix": unrecognized import path "golang.org/x/sys"

    问题描述 当我们使用 go get.go install.go mod 等命令时,会自动下载相应的包或依赖包.但由于众所周知的原因,类似于 golang.org/x/... 的包会出现下载失败的情况. ...

  3. php数据库操作代码

    数据库名为reg,表名为member,字段名为username,password,regdate <?php $link=@mysql_connect("localhost" ...

  4. selenium遇到不可编辑input和隐藏input如何赋值

    js = 'document.getElementsByName("m:csr_mt_gnccspjclfbxd:bmshldID")[0].type="text&quo ...

  5. 排序算法之高速排序(Java)

    //高速排序 public class Quick_Sort { // 排序的主要算法 private int Partition(int[] data, int start, int end) { ...

  6. Linux中ping不通外网

    在linux中ping www.baidu.com 无法ping通 需要修改vi /etc/resolv.conf 增加如下内容: nameserver 114.114.114.114 nameser ...

  7. 分享下多年积累的对JAVA程序员成长之路的总结

    http://blog.csdn.net/zhongzelin/article/details/8643269我也搞了几年JAVA了,由于一向懒惰,没有成为大牛,只是一普通程序猿,不爱玩社交网站,不爱 ...

  8. Android网络编程Socket【实例解析】

    Socket 事实上和JavaWeb 里面的Socket一模一样 建立客服端,server端,server开一个port供客服端訪问 第一步创建server端:(这里把为了便于解说.把server端, ...

  9. Sping Boot入门到实战之实战篇(二):一些常用功能的Spring Boot Starters

    包含功能 阿里云消息服务MNS 阿里云消息队列服务(即时消息.延迟消息.事务消息) AOP日志 基于MyBatis通用Mapper及DRUID的数据库访问 dubbo支持 错误处理 七牛图片服务 re ...

  10. rtems 4.11 时钟驱动(arm, beagle)

    根据bsp_howto手册,时钟驱动的框架主要在 c/src/lib/libbsp/shared/Clockdrv_shell.h 文件中实现 时钟初始化 时钟驱动初始化函数为 Clock_initi ...