介绍

随着业务的发展,除了拆分业务模块外,数据库的读写分离也是常见的优化手段。
方案使用了AbstractRoutingDataSourcemybatis plugin来动态的选择数据源
选择这个方案的原因主要是不需要改动原有业务代码,非常友好

注:
demo中使用了mybatis-plus,实际使用mybatis也是一样的
demo中使用的数据库是postgres,实际任一类型主从备份的数据库示例都是一样的
demo中使用了alibaba的druid数据源,实际其他类型的数据源也是一样的
复制代码

环境

首先,我们需要两个数据库实例,一为master,一为slave。
所有的写操作,我们在master节点上操作
所有的读操作,我们在slave节点上操作

需要注意的是:对于一次有读有写的事务,事务内的读操作也不应该在slave节点上,所有操作都应该在master节点上
复制代码

先跑起来两个pg的实例,其中15432端口对应的master节点,15433端口对应的slave节点:

docker run \
--name pg-master \
-p 15432:5432 \
--env 'PG_PASSWORD=postgres' \
--env 'REPLICATION_MODE=master' \
--env 'REPLICATION_USER=repluser' \
--env 'REPLICATION_PASS=repluserpass' \
-d sameersbn/postgresql:10-2 docker run \
--name pg-slave \
-p 15433:5432 \
--link pg-master:master \
--env 'PG_PASSWORD=postgres' \
--env 'REPLICATION_MODE=slave' \
--env 'REPLICATION_SSLMODE=prefer' \
--env 'REPLICATION_HOST=master' \
--env 'REPLICATION_PORT=5432' \
--env 'REPLICATION_USER=repluser' \
--env 'REPLICATION_PASS=repluserpass' \
-d sameersbn/postgresql:10-2

实现

整个实现主要有3个部分:

  • 配置两个数据源
  • 实现AbstractRoutingDataSource来动态的使用数据源
  • 实现mybatis plugin来动态的选择数据源

配置数据源

将数据库连接信息配置到application.yml文件中

spring:
mvc:
servlet:
path: /api datasource:
write:
driver-class-name: org.postgresql.Driver
url: "${DB_URL_WRITE:jdbc:postgresql://localhost:15432/postgres}"
username: "${DB_USERNAME_WRITE:postgres}"
password: "${DB_PASSWORD_WRITE:postgres}"
read:
driver-class-name: org.postgresql.Driver
url: "${DB_URL_READ:jdbc:postgresql://localhost:15433/postgres}"
username: "${DB_USERNAME_READ:postgres}"
password: "${DB_PASSWORD_READ:postgres}" mybatis-plus:
configuration:
map-underscore-to-camel-case: true

write写数据源,对应到master节点的15432端口
read读数据源,对应到slave节点的15433端口

将两个数据源信息注入为DataSourceProperties

@Configuration
public class DataSourcePropertiesConfig { @Primary
@Bean("writeDataSourceProperties")
@ConfigurationProperties("datasource.write")
public DataSourceProperties writeDataSourceProperties() {
return new DataSourceProperties();
} @Bean("readDataSourceProperties")
@ConfigurationProperties("datasource.read")
public DataSourceProperties readDataSourceProperties() {
return new DataSourceProperties();
}
}

实现AbstractRoutingDataSource

spring提供了AbstractRoutingDataSource,提供了动态选择数据源的功能,替换原有的单一数据源后,即可实现读写分离:

@Component
public class CustomRoutingDataSource extends AbstractRoutingDataSource { @Resource(name = "writeDataSourceProperties")
private DataSourceProperties writeProperties; @Resource(name = "readDataSourceProperties")
private DataSourceProperties readProperties; @Override
public void afterPropertiesSet() {
DataSource writeDataSource =
writeProperties.initializeDataSourceBuilder().type(DruidDataSource.class).build();
DataSource readDataSource =
readProperties.initializeDataSourceBuilder().type(DruidDataSource.class).build(); setDefaultTargetDataSource(writeDataSource); Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(WRITE_DATASOURCE, writeDataSource);
dataSourceMap.put(READ_DATASOURCE, readDataSource);
setTargetDataSources(dataSourceMap); super.afterPropertiesSet();
} @Override
protected Object determineCurrentLookupKey() {
String key = DataSourceHolder.getDataSource(); if (key == null) {
// default datasource
return WRITE_DATASOURCE;
} return key;
} }

AbstractRoutingDataSource内部维护了一个Map<Object, Object>的Map
在初始化过程中,我们将write、read两个数据源加入到这个map
调用数据源时:determineCurrentLookupKey()方法返回了需要使用的数据源对应的key

当前线程需要使用的数据源对应的key,是在DataSourceHolder类中维护的:

public class DataSourceHolder {

    public static final String WRITE_DATASOURCE = "write";
public static final String READ_DATASOURCE = "read"; private static final ThreadLocal<String> local = new ThreadLocal<>(); public static void putDataSource(String dataSource) {
local.set(dataSource);
} public static String getDataSource() {
return local.get();
} public static void clearDataSource() {
local.remove();
} }

实现mybatis plugin

上面提到了当前线程使用的数据源对应的key,这个key需要在mybatis plugin根据sql类型来确定 MybatisDataSourceInterceptor类:

@Component
@Intercepts({
@Signature(type = Executor.class, method = "update",
args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class,
CacheKey.class, BoundSql.class})})
public class MybatisDataSourceInterceptor implements Interceptor { @Override
public Object intercept(Invocation invocation) throws Throwable { boolean synchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();
if(!synchronizationActive) {
Object[] objects = invocation.getArgs();
MappedStatement ms = (MappedStatement) objects[0]; if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
if(!ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
DataSourceHolder.putDataSource(DataSourceHolder.READ_DATASOURCE);
return invocation.proceed();
}
}
} DataSourceHolder.putDataSource(DataSourceHolder.WRITE_DATASOURCE);
return invocation.proceed();
} @Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
} @Override
public void setProperties(Properties properties) {
}
}

仅当未在事务中,并且调用的sql是select类型时,在DataSourceHolder中将数据源设为read
其他情况下,AbstractRoutingDataSource会使用默认的write数据源

至此,项目已经可以自动的在读、写数据源间切换,无需修改原有的业务代码
最后,提供demo使用依赖版本

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatisplus-spring-boot-starter</artifactId>
<version>1.0.5</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>2.1.9</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

springboot+mybatis实现数据库的读写分离的更多相关文章

  1. MyBatis多数据源配置(读写分离)

    原文:http://blog.csdn.net/isea533/article/details/46815385 MyBatis多数据源配置(读写分离) 首先说明,本文的配置使用的最直接的方式,实际用 ...

  2. Mycat - 实现数据库的读写分离与高可用

    前言 开心一刻 上语文课,不小心睡着了,坐在边上的同桌突然叫醒了我,并小声说道:“读课文第三段”.我立马起身大声读了起来.正在黑板写字的老师吓了一跳,老师郁闷的看着我,问道:“同学有什么问题吗?”,我 ...

  3. 如何轻松实现MySQL数据库的读写分离和负载均衡?

    配置好了 Mysql 的主从复制结构后,我们希望实现读写分离,把读操作分散到从服务器中,并且对多个从服务器能实现负载均衡.读写分离和负载均衡是 Mysql 集群的基础需求,MaxScale 就可以帮着 ...

  4. spring+mybatis+mysql5.7实现读写分离,主从复制

    申明:请尽量与我本博文所有的软件版本保持一致,避免不必要的错误. 所用软件版本列表:MySQL 5.7spring5mybaties3.4.6 首先搭建一个完整的spring5+springMVC5+ ...

  5. 利用oneproxy部署mysql数据库的读写分离

    实验系统:CentOS 6.6_x86_64 实验前提:防火墙和selinux都关闭 实验说明:本实验共有4台主机,IP分配如拓扑 实验软件:mariadb-10.0.20 oneproxy-rhel ...

  6. MySQL搭建主从数据库 实现读写分离

    首先声明,实际生产中,网站为了提高用户体验,性能等,将数据库实现读写分离是有必要的,我们让主数据库去写入数据,然后当用户查询的时候,然后在从数据库读取数据,故能减轻数据库的压力,实现良好的用户体验! ...

  7. 基于 EntityFramework 的数据库主从读写分离服务插件

    基于 EntityFramework 的数据库主从读写分离服务插件 1. 版本信息和源码 1.1 版本信息 v1.01 beta(2015-04-07),基于 EF 6.1 开发,支持 EF 6.1 ...

  8. 基于 EntityFramework 的数据库主从读写分离架构 - 目录

    基于 EntityFramework 的数据库主从读写分离架构       回到目录,完整代码请查看(https://github.com/cjw0511/NDF.Infrastructure)中的目 ...

  9. 基于 EntityFramework 的数据库主从读写分离架构(1) - 原理概述和基本功能实现

        回到目录,完整代码请查看(https://github.com/cjw0511/NDF.Infrastructure)中的目录:      src\ NDF.Data.EntityFramew ...

随机推荐

  1. Linux 系统管理——Linux文件系统与日志

    1.inode 包含文件的元信息(1)inode 内容:文件的字节数.拥有者的 UID.GID.文件的读写执行权限.时间戳等,但不包含文件名.文件名是储存在目录的目录项中.(2)查看文件的 inode ...

  2. .NET总结--ASP.NET工作原理

    前言 前前后后写了不少关于某些技术啥的博客,一直在追新求深,而真正使用上的时候才发现了解的太少太少了,从事.net开发三年有余了不是它不行了而是我坚持不住了,如今不得不向生活低头,这个系列作为三年技术 ...

  3. java stackoverflowerror与outofmemoryerror区别

    1.stackoverflow: 每当java程序启动一个新的线程时,java虚拟机会为他分配一个栈,java栈以帧为单位保持线程运行状态:当线程调用一个方法是,jvm压入一个新的栈帧到这个线程的栈中 ...

  4. 记录一次JVM配置优化的案例

    上周公司有一个应用,一到晚上高峰期的时候RT(响应时间)就很长.后来上服务器看了下JVM的配置,发现运维在启动参数那里把-Xss给设成了10M.导致每个线程占用的内存过大,导致内存消耗过快,其它线程排 ...

  5. SQLSERVER获取数据库中的所有表的名称、表中所有字段的属性

    1.查询数据库中的所有数据库名: SELECT Name FROM Master..SysDatabases ORDER BY Name 2.查询某个数据库中所有的表名: SELECT Name FR ...

  6. DIP大作业---图像分割

    数字图像处理课程的大作业,要求如下: 图像分割就是把图像分成若干个特定的.具有独特性质的区域并提出感兴趣目标的技术和过程.它是由图像处理到图像分析的关键步骤.现有的图像分割方法主要分以下几类:基于阈值 ...

  7. HashSet Integer输出有序,String输出无序

    1. 背景自己在测试HashSet时,发现其输出Integer是有序的.2. 测试public static void hashSetObjectTest(){ Set<Integer> ...

  8. 灵活的MyBatis

    一.前言 将数据存储到数据库是开发中很重要的一环.曾经有程序员说自己做过最牛逼的事情就是增删改查.确实我们做了很多页面,后太代码写了很多,可是最终都离不开数据库的增删改查.Java有一套自己的JPA标 ...

  9. ELK(Elasticsearch + Logstash + Kibana) 日志收集

    单体应用或微服务的场景下,每个服务部署在不同的服务器上,需要对日志进行集重收集,然后统一查看所以日志. ELK日志收集流程: 1.微服务器上部署Logstash,对日志文件进行数据采集,将采集到的数据 ...

  10. [技术博客]基于动态继承类、WebDriver的浏览器兼容性测试框架搭建

    问题背景 观察使用selenium进行自动化测试的过程,我们可以将它概述为: 启动测试进程,在该进程中构建WebDriver 启动浏览器进程,将它与WebDriver建立连接 使用WebDriver向 ...