1、什么是数据库读写分离?

数据库读写分离就是将数据库分成主从库,主库用来写入数据并对数据进行增删改的维护。从库通过某种同步机制从主库同步数据,即主库的完全镜像备份。读写分离一般是配置一个主库多个从库,它是一种常见的数据库架构设计。

2、数据库读写分离解决什么问题?

数据库读写分离主要是解决业务中对数据读性能的瓶颈。因为大多数系统一般情况下是读多写少,这个时候读操作会首先成为数据库服务的瓶颈,间接也会导致数据库的写入出现问题。一般互联网系统最开始采用的是单数据库的设计架构,也就是读写公用一个数据库,但是随着业务的扩展以及系统用户的增多,单数据库就会出现读写卡顿,甚至业务操作失败的情况。此时读写分离的设计思路就能在一定程度上满足系统的需求。正确使用读写分离的数据库架构,可以线性的提升数据库读操作的性能瓶颈,同时也能解决读写锁冲突对写入操作的影响而提升写操作的性能。

注:读写分离只是在一定程度上解决系统因业务以及用户体量的增加而暂时解决数据库性能冲突的一种设计思路,但它并不能彻底解决这个问题。当系统体量足够大的时候,读写分离也将无法应对系统的性能问题。而此时就需要其他的设计思路解决,比如数据库的分库,分区,分表等。

3、读写分离代码实现(springboot+mybatis+mysql)

一、首先需要先配置myslql的主从库,此配置可参见之前写过的这一篇文章

二、代码实现
1、在idea中新建一个springboot工程

2、在pom.xml中加入以下依赖注入的配置信息

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 使用TkMybatis可以无xml文件实现数据库操作,只需要继承tkMybatis的Mapper接口即可-->
<dependency><groupId>tk.mybatis</groupId><artifactId>mapper-spring-boot-starter</artifactId><version>1.1.4</version>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.0.9</version>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>

3、在src/main/resources目录下新建application.yml文件,并配置如下信息

server:port: 8082servlet:context-path: /dxfl
spring:datasource:#读库数目maxReadCount: 1type-aliases-package: com.teamo.dxfl.mappermapper-locations: classpath:/mapper/*.xmlconfig-location: classpath:/mybatis-config.xmlwrite:url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=trueusername: rootpassword:driver-class-name: com.mysql.jdbc.DriverinitialSize: 2           #初始化大小maxWait: 6000        #获取连接时最大等待时间,单位毫秒。min-idle: 5            # 数据库连接池的最小维持连接数maxActive: 20         # 最大的连接数initial-size: 5          # 初始化提供的连接数max-wait-millis: 200    # 等待连接获取的最大超时时间read1:url: jdbc:mysql://localhost:3307/test?useUnicode=true&characterEncoding=utf-8&useSSL=trueusername: rootpassword:driver-class-name: com.mysql.jdbc.DriverinitialSize: 2          #初始化大小maxWait: 6000       #获取连接时最大等待时间,单位毫秒。min-idle: 5           # 数据库连接池的最小维持连接数maxActive: 20        # 最大的连接数initial-size: 5         # 初始化提供的连接数max-wait-millis: 200   # 等待连接获取的最大超时时间

4、编写数据源的配置类(在config目录下)
DataSourceConfig.java

@Configuration
public class DataSourceConfig {@Value("${spring.datasource.type-aliases-package}")private String typeAliasesPackage;@Value("${spring.datasource.mapper-locations}")private String mapperLocation;@Value("${spring.datasource.config-location}")private String configLocation;/*** 写数据源* @Primary 标志这个 Bean 如果在多个同类 Bean 候选时,该 Bean 优先被考虑。* 多数据源配置的时候注意,必须要有一个主数据源,用 @Primary 标志该 Bean*/@Primary@Bean@ConfigurationProperties(prefix = "spring.datasource.write")public DataSource writeDataSource() {return new DruidDataSource();}/*** 读数据源*/@Bean@ConfigurationProperties(prefix = "spring.datasource.read1")public DataSource readDataSource1() {return new DruidDataSource();}/*** 多数据源需要自己设置sqlSessionFactory*/@Beanpublic SqlSessionFactory sqlSessionFactory() throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(routingDataSource());ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();// 实体类对应的位置bean.setTypeAliasesPackage(typeAliasesPackage);// mybatis的XML的配置bean.setMapperLocations(resolver.getResources(mapperLocation));bean.setConfigLocation(resolver.getResource(configLocation));return bean.getObject();}/*** 设置数据源路由,通过该类中的determineCurrentLookupKey决定使用哪个数据源*/@Beanpublic AbstractRoutingDataSource routingDataSource() {RoutingDataSourceConfig proxy = new RoutingDataSourceConfig();DataSource writeDataSource = writeDataSource();//设置数据源Map对象Map<Object, Object> dataSource = new HashMap<Object, Object>(2);dataSource.put(DataBaseTypeEnum.WRITE.getCode(), writeDataSource);//如果配置了多个读数据源,就一次添加到datasource对象中dataSource.put(DataBaseTypeEnum.READ.getCode()+"1", readDataSource1());//写数据源设置为默认数据源proxy.setDefaultTargetDataSource(writeDataSource);proxy.setTargetDataSources(dataSource);return proxy;}/*** 设置事务,事务需要知道当前使用的是哪个数据源才能进行事务处理(配置mybatis时候用到)*/@Beanpublic DataSourceTransactionManager dataSourceTransactionManager() {return new DataSourceTransactionManager(routingDataSource());}
}

5、编写数据源路由的配置类(在config目录下),该类继承AbstractRoutingDataSource并重写determineCurrentLookupKey()方法,该方法实现数据源选择的逻辑。同时该类持有一个ThreadLocal对象,该对象用于存储当前线程使用的数据源类型是读或者写。可以自己编写一定的算法实现读库的负载均衡(加入读库配置了多个),此处简单的通过获取随机数的方式来选择使用哪一个读库数据源。
DataSourceRouteConfig.java

public class RoutingDataSourceConfig extends AbstractRoutingDataSource {//使用ThreadLocal对象保存当前线程是否处于读模式private static ThreadLocal<String> DataBaseMap= new ThreadLocal<>();@Value("${spring.datasource.maxReadCount}")private int maxReadCount;private final Logger log = LoggerFactory.getLogger(this.getClass());@Overrideprotected Object determineCurrentLookupKey() {String typeKey = getDataBaseType();if (typeKey == DataBaseTypeEnum.WRITE.getCode()) {log.info("使用了写库");return typeKey;}//使用随机数决定使用哪个读库int index = (int) Math.floor(Math.random() * (maxReadCount - 1 + 1)) + 1;;log.info("使用了读库{}", index);return DataBaseTypeEnum.READ.getCode() + index;}public static void setDataBaseType(String dataBaseType) {DataBaseMap.set(dataBaseType);}public static String getDataBaseType() {return DataBaseMap.get() == null ? DataBaseTypeEnum.WRITE.getCode() : DataBaseMap.get();}public static void clearDataBaseType() {DataBaseMap.remove();}
}

6、我们需要编写一个Annotation类,专门用于标注Service类哪些方法使用读数据源,哪些方法使用写数据源。(除此种方法外,还可以在aop类中通过配置匹配方法名的方式,例如通配save*,update*,delete*的方法匹配写数据源,其它方法为读数据源,此处只考虑Annotation注解的方式)。

ReadDB.java
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadDB {//String value() default "";
}

7、编写aop类,在请求开启事务之前,使用spring的aop(面向切面编程)先判断Service方法上是否注解ReadDB,如果方法上进行了ReadDB注解,则选择读数据源。此类实现了Ordered接口并重写getOrder()方法,该接口是通知Spring调用类执行方法顺序,getOrder()方法返回的值越小,优先级越高。此处需要注意getOrder()方法返回的值一定要小于在工程启动类中对@EnableTransactionManagement(order=)对设置的值,以确保选择数据源的操作在开启事务之前执行。
DxflApplication.java

@SpringBootApplication
@EnableTransactionManagement(order = 5)
public class DxflApplication {public static void main(String[] args) {SpringApplication.run(DxflApplication.class, args);}
}

ReadDBAspect.java

@Aspect
@Component
public class ReadDBAspect implements Ordered {private static final Logger log= LoggerFactory.getLogger(ReadDBAspect.class);@Around("@annotation(readDB)")public Object setRead(ProceedingJoinPoint joinPoint, ReadDB readDB) throws Throwable {try{//设置读数据源RoutingDataSourceConfig.setDataBaseType(DataBaseTypeEnum.READ.getCode());return joinPoint.proceed();}finally {log.info("清除dataSource类型选中值:"+DataBaseTypeEnum.READ.getData());RoutingDataSourceConfig.clearDataBaseType();}}@Overridepublic int getOrder() {return 0;}
}

8、编写Service方法,在读数据的方法上添加@ReadDB注解,标注方法为读库方法。
UserServiceImpl.java

@Service
public class UserviceServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Override@ReadDBpublic List<User> getAllUser() {return userMapper.selectAll();}@Override@Transactional(rollbackFor = RuntimeException.class)public boolean save(User user){if(userMapper.insert(user)>0){return true;}else{return false;}//throw new RuntimeException("测试事务");}
}

9、编写Controller类调用Service方法,实现读写方法的测试。
UserController.java

@RestController
@RequestMapping("/user")
public class UserController{@Autowiredprivate UserService userService;@GetMapping("getAllUser")public List<User> getAllUser() {List resList = userService.getAllUser();return resList;}@PostMapping("save")public Result save(@RequestBody User user){Result result = new Result();if(null != user){try{userService.save(user);result.setCode(ResultEnum.SUCCESS.getCode());result.setMsg("保存用户成功!");}catch(Exception e) {e.printStackTrace();result.setCode(ResultEnum.SUCCESS.getCode());result.setMsg("保存用户失败,原因:"+ e.getMessage() +"!");}}else{result.setCode(ResultEnum.SUCCESS.getCode());result.setMsg("保存用户失败,原因:用户数据为空!");}return result;}
}

10、测试
成功启动服务后,测试读库:在浏览器输入地址,http://localhost:8089/dxfl/user/getAllUser,显示如下:
查看后台打印:显示使用了读库
测试写库
测试写库需要进行form提交,此时需要一个测试工具,此处使用的是ApiPost。打开测试工具,在测试栏里输入保存用户的接口,提交方式选择application/json,然后点击发送按钮,带响应出成功返回信息,并提示保存用户成功则代表写入测试成功。
后端日志打印信息为:使用了写库,则表名测试写库成功。
为了成功验证读写分离是否真正成功,在测试写库的时候可以先关闭总从库的功能(stop slave),待确保写入的是指定的写库后再开启主从库功能(start slave)。

springBoot+myBatis配置基于mysql的读写分离相关推荐

  1. SpringBoot下MySQL的读写分离

    首页 博客 专栏·视频 下载 论坛 问答 代码 直播 能力认证 高校 会员中心 收藏 动态 消息 创作中心 02-下篇-SpringBoot下MySQL的读写分离 dusuanyun 2018-07- ...

  2. mybatis获取mysql源数据类型_spring集成mybatis实现mysql数据库读写分离

    前言 在网站的用户达到一定规模后,数据库因为负载压力过高而成为网站的瓶颈.幸运的是目前大部分的主流数据库都提供主从热备功能,通过配置两台数据库主从关系,可以将一台数据库的数据更新同步到另一台服务器上. ...

  3. MySQL 案例实战--MySQL 基于Mycat实现读写分离

    MySQL 基于Mycat实现读写分离 前言 一.什么是读写分离? 二.MySQL 读写分离解决方案 三.MySQL 基于Mycat实现读写分离 四.Mycat-web 管理部署 前言 本环境是基于 ...

  4. mysql proxy 主从_【MYSQL知识必知必会】MySQL主从复制读写分离(基于mysql-proxy实现)...

    MySQL主从复制读写分离(基于mysql-proxy实现) http://mirror.bit.edu.cn/mysql/Downloads/MySQL-Proxy/mysql-proxy-0.8. ...

  5. Amoeba实现mysql主从读写分离

    Amoeba实现mysql主从读写分离 这段在网上看了下关于amoeba的文章,总体感觉好像要比mysql-proxy好的多,也参考了不少的资料,此文章可能与其他文章作者会有雷同的地方,请谅解,但是此 ...

  6. MySQL Router实现MySQL的读写分离

    1.简介 MySQL Router是MySQL官方提供的一个轻量级MySQL中间件,用于取代以前老版本的SQL proxy. 既然MySQL Router是一个数据库的中间件,那么MySQL Rout ...

  7. mysql数据库字段变形_详解如何利用amoeba(变形虫)实现mysql数据库读写分离

    摘要:这篇MySQL栏目下的"详解如何利用amoeba(变形虫)实现mysql数据库读写分离",介绍的技术点是"MySQL数据库.数据库读写分离.amoeba.MySQL ...

  8. amoeba实现mysql主从读写分离_利用Amoeba实现MySQL主从复制和读写分离

    在实际生产环境中,如果对数据库的读和写都在同一个数据库服务器中操作,无论是在安全性.高可用性,还是高并发等各个方面都是完全不能满足实际需求的,因此,一般来说都是通过主从复制(Master-Slave) ...

  9. Mycat原理详解,Mycat 实现 MySQL 的读写分离(Mysql主从复制)

    文章目录 1 Mycat 介绍 2 Mycat 安装 2.1下载安装JDK 2.2下载安装mycat 2.3启动和连接 3 Mycat 主要配置文件说明 4 利用 Mycat 实现 MySQL 的读写 ...

最新文章

  1. 百篇大计敬本年之C++坎坷之路 —— Warning:will be initialized after [-Wreorder]
  2. python pandas库——pivot使用心得
  3. 【陷阱】交换排序中交换动作的陷阱
  4. c语言入口及出口参数说明,麻烦帮忙指出一下这个函数的入口参数和出口参数呀!...
  5. mplus 软件_Mplus 7.4 软件及代码
  6. table control的修改/排序/删除功能实现实例
  7. idea 内存溢出解决方法
  8. 总账分录追溯发票或者付款
  9. 吴恩达《机器学习》学习笔记九——神经网络相关(1)
  10. ThinkPHP6项目基操(10.不可预知的內部异常处理)
  11. 美团面试官:讲清楚MySQL结构体系,立马发offer
  12. 抠图为什么要用绿布_逆天抠图,Photoshop 2020天神下凡
  13. Paint的方法总结(一):基本常用Api
  14. 阶段3 2.Spring_09.JdbcTemplate的基本使用_2 JdbcTemplate的概述和入门
  15. Excel 多级下拉菜单设置,数据有效性
  16. Unity3D中的动态字体和静态字体
  17. 如何用VBA保护工作表
  18. npm : 无法将“npm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。
  19. 二、操作系统基本原理
  20. emacs chinese manual

热门文章

  1. mysql sqlstate 08001_DB2错误:SQLCODE=-30082, SQLSTATE=08001
  2. iOS-方形图片设置成圆形图片
  3. 正方形图片变圆形css
  4. 如何预防计算机保护数据,怎么防止别人拷贝电脑文件?计算机数据如何防泄密...
  5. 电子科技大学计算机16级公示,物光院关于受理2021年博士学位申请工作的通知
  6. linux 电池管理软件,Linux电源管理笔记本模式工具1.65来延长电池续航能力
  7. 转载: PostgreSQL SQL的性能调试方法2--数据库log分析
  8. html设置margin无效,CSS中margin不起作用的原因及解决方法
  9. .jks文件(JAVA KeyStore)
  10. 网络变压器有方向吗?