前言
上一篇发了手动搭建Redis集群和MySQL主从同步(非Docker)之后,很多同学对文中主从结构提到的读写分离感兴趣,本打算在双十一期间直接把读写分离分享给大家,奈何工作一直没停下,所以这周抽空把这些分享出来。
关于MySQL的读写分离的实现,有两种方式,第一种方式即我们手动在代码层实现逻辑,来解析读请求或者写请求,分别分发到不同的数据库中,实现读写分离;第二种方式就是基于MyCat中间件来实现读写分离的效果;这两种方式我都会在这篇博客中进行详细地介绍、搭建,并且分析其中的优劣。
原理初探
从MySQL的主从同步开始谈起,最开始我们的数据库架构是这样的。
主库负责了所有的读写操作,而从库只对主库进行了备份,就像我在上一篇文章中说的那样,我认为如果只实现了一个备份,不能读写分离和故障转移,不能降低Master节点的IO压力,这样的主从架构看起来性价比似乎不是很高。
我们所希望的主从架构是,当我们在写数据时,请求全部发到Master节点上,当我们需要读数据时,请求全部发到Slave节点上。并且多个Slave节点最好可以存在负载均衡,让集群的效率最大化。
那么这样的架构就不够我们使用了,我们需要找寻某种方式,来实现读写分离。那么实际上有两种方式。
方法1:代码层实现读写分离
这种方法的优势就是比较灵活,我们可以按照自己的逻辑来决定读写分离的规则。如果使用了这样的方法,我们整个数据库的架构就可以用下面这张图进行概括:
方法2:使用中间层(虚拟节点)进行请求的转发
这种方式最主要的特点就是我们在除了数据库以外地方,新构建了一个虚拟节点,而我们所有的请求都发到这个虚拟节点上,由这个虚拟节点来转发读写请求该相应的数据库。
这种方式的特点就是,其构建了一个独立的节点来接收所有的请求,而不用在我们的程序中配置多数据源,我们的项目只需要将url指向这个虚拟节点,然后由这个虚拟节点来处理读写请求。不是有这么一句话吗,专业的事交给专业的人来做,大概是这么个意思吧。而现在存在的MyCat等中间件,就是这样的一个”专业的人“。
那么下面我就会动手实现上述两个读写分离的解决方案,代码层实现读写分离和使用中间件实现读写分离
手动实现读写分离
实现读写分离的方法有很多,我这里会说到两种,第一种是使用MyBatis和Spring,手写MyBatis拦截器来判断SQL是读或者写,从而选择数据源,最后交给Spring注入数据源,来实现读写分离;第二种是使用MyCat中间件,配置化地实现读写分离,每种方式都有其可取之处,可以自己视情况选用。
环境说明
这里用到了我的上篇博客手动搭建Redis集群和MySQL主从同步(非Docker)中所搭建的MySQL主从同步,如果手上没有这套环境的,可以先比着这篇博客进行搭建。但是需要
注意的是,要将8.0版本的MySQL改为5.7。
192.168.43.201:3306 Master
192.168.43.202:3306 Slave
开发环境:
IDE:Eclipse
Spring boot 2.1.7
MySQL 5.7
CentOS 7.3
新建Maven项目
为了演示方便,这里使用SpringBoot作为测试的基础框架,省去了很多Spring需要的xml配置。没有用过SpringBoot的同学也没关系,我会一步一步地进行演示操作。
导入依赖
<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> <!-- Web相关 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 数据库相关 --> <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc7</artifactId> <version>12.1.0</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- 测试相关依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <!-- --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId> spring-boot-configuration-processor </artifactId> <optional>true</optional> </dependency> </dependencies>
application.yml
为了测试项目尽量简单,所以我们不用去过多地配置其它东西。只有一些基本配置和数据源配置。
server: port: 10001 spring: datasource: url: jdbc:mysql://192.168.43.201:3306/springtestdemo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC username: Object password: Object971103. driver-class-name: com.mysql.cj.jdbc.Driver #MyBatis配置 mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true
编写启动类
@SpringBootApplication public class ApplicationStarter { public static void main(String[] args) { SpringApplication.run(ApplicationStarter.class, args); } }
启动
出现以上信息代表启动成功。嗯……这应该是一个数据库相关的博客,好像讲了太多的SpringBoot
到这里说明我们的SpringBoot项目没有问题,已经搭建成功,如果还不放心,可以自行访问一下http://localhost:10001这个路径,如果出现SpringBoot的404,则代表启动成功。
新建Student实体并创建数据库
package cn.objectspace.springtestdemo.domain; public class Student { private String studentId; private String studentName; public String getStudentId() { return studentId; } public void setStudentId(String studentId) { this.studentId = studentId; } public String getStudentName() { return studentName; } public void setStudentName(String studentName) { this.studentName = studentName; } }
CREATE TABLE student( student_id VARCHAR(32), student_name VARCHAR(32) );