shardingsphere之sharding-jdbc读写分离学习笔记

shardingsphere之sharding-jdbc读写分离学习笔记

  • 引言
  • 重要提示
    • 演示环境
    • 版本信息
    • 代码示例和参考
  • 读写分离简介
    • 主从复制
      • 主从复制的原理和流程
  • sharding-jdbc简介
    • 概念
    • 使用情况
  • 从零开始整合sharding-jdbc
    • docker的安装
    • 安装mysql并配置主从复制
      • 拉取mysql镜像
      • 安装配置master库
      • slave数据库安装配置
      • 出现异常参考
      • 开放阿里云安全组端口
      • 使用客户端连接
      • 简单测试
    • 数据库初始化
    • 编写代码
      • 出现异常参考
  • 结语
  • 相关链接

引言

读写分离在业务中应该是比较常见的,当满足以下几点就可以考虑将数据进行读写分离,减轻数据库的压力和提高响应速度。

(1)业务对数据库是读多写少。
(2)单台服务器或者单个数据库的性能已经不能满足当前业务对数据库大量读取请求。

重要提示

演示环境

  • 阿里云ECS服务器
  • 系统版本:CentOS Linux release 7.7.1908 (Core)
  • docker版本:Docker version 19.03.8, build afacb8b

版本信息

  • spring-boot 2.1.9.RELEASE
  • mybatis-plus-generator 3.1.2
  • mybatis-plus 3.1.2
  • mybatis-plus-boot-starter 3.1.2
  • druid-spring-boot-starter 1.1.18
  • sharding-jdbc-spring-boot-starter 4.0.0-RC1
  • mysql 8.0.20

代码示例和参考

用于演示的代码和相关的参考链接已经放到文章的末尾。其中在演示的代码中,sql文件放置在sql文件夹,请求信息放置在postMan文件夹,导入postman执行即可。

读写分离简介

读写分离主要分为两步,一是设置数据库实现主从复制,二是在代码层面实现增删改操作到主数据库,读的操作到从数据库,提高数据库的响应能力。

主从复制

太多的理论知识就不在这里赘述了,可以参考MYSQL主从复制原理,有兴趣的可以进行深入研究,选择适合自己的解决方案。

主从复制的原理和流程

shardingsphere之sharding-jdbc读写分离学习笔记_第1张图片

(1)Master主库将数据变更DataChanges记录 binlog日志中。
(2)Slave起一个I/O线程连接到Master,dump读取Master的binlog日志并写入到Slave的中继日志Relaylog中
(3)Slave中的SQL线程读取中继日志Relaylog进行SQL回放执行操作,完成主从复制,保证主从最终一致性。

sharding-jdbc简介

概念

太多的理论知识我就不赘述了,麻烦自己到官网去看。

在这里插入图片描述

使用情况

目前生产环境已使用的公司
在这里插入图片描述

从零开始整合sharding-jdbc

本文是基于阿里云ECS服务器上的docker搭建数据库的,使用docker安装会比传统的方式效率高很多。
下面会把详细的安装步骤和注意的地方一一列出来。这里演示的是一主三从,一个主库master,三个从库slave0,slave1,slave2。

小插曲

对于mysql的安装,一开始我是选择在windows7下面用docker安装的。在安装docker的时候出现一大堆问题,初始化也很麻烦,搞了一个下午也没搞完。所以我在转到阿里云上面安装了。当然也可以windows下使用解压的方式安装mysql,这个就自行谷歌吧。

docker的安装

(1)卸载旧的docker,可选。

sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-selinux \
                  docker-engine-selinux \
                  docker-engine

(2)安装yum工具类

sudo yum install -y yum-utils \
  device-mapper-persistent-data \
  lvm2

(3)启用Docker源

sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo

(4)安装Docker

sudo yum -y install docker-ce

(5)配置国内的Docker仓库,加快Docker镜像下载速度

vim /etc/docker/daemon.json

{
  "registry-mirrors" : [
    "https://registry.docker-cn.com"
  ]
}

(6)启动docker

sudo systemctl start docker

(7) 检查是否安装成功,如果出现正常的信息则表示安装成功。

sudo docker info

shardingsphere之sharding-jdbc读写分离学习笔记_第2张图片

安装mysql并配置主从复制

拉取mysql镜像

(1)查看mysql镜像版本

可以直接访问 MySQL镜像库地址,直接复制对应的命令即可拉取镜像。
shardingsphere之sharding-jdbc读写分离学习笔记_第3张图片

或者使用命令查看

docker search mysql

shardingsphere之sharding-jdbc读写分离学习笔记_第4张图片(2)拉取镜像

docker pull mysql:8.0.20

(3)查看已拉取的镜像

docker images

在这里插入图片描述

安装配置master库

(1)运行mysql容器

docker run -itd --name master_3307 -p 3307:3306 -e MYSQL_ROOT_PASSWORD=root mysql:8.0.20

参数介绍

  • –name master_3307 表示容器的名称为master_3307
  • -p 3307:3306 表示将宿主机的3307端口映射到docker的3306端口
  • -e MYSQL_ROOT_PASSWORD=root 表示设置用户root的密码为root
  • mysql:8.0.20 表示使用的镜像版本

小插曲

其实一个镜像可以复用安装成多个容器的,一开始我还以为一个镜像只能对应一个容器呢。

(2)检查是否安装成功

docker ps 

下图是成功安装了master,slave0,slave1,slave2结果。
在这里插入图片描述简单备注

docker pull mysql:8.0.20 和docker pull mysql:8.0其实拉取的是同一版本的镜像,这个看镜像ID一致即可知道。

(3)修改配置信息

进入容器内部

docker exec -it master_3307 bash

更新基础软件和安装vim

apt-get update
apt-get install vim

编辑配置文件

vi /etc/mysql/my.cnf

新增信息

[mysqld]
## 同一局域网内注意要唯一
server-id=100  
## 开启二进制日志功能,可以随便取(关键)
log-bin=mysql-bin
## 永久设置时区时间
default-time_zone = '+8:00'

修改后保存退出编辑

:wq

备注

  • 按照上面顺序操作可以避免下方两个提示。
bash: vi: command not found
Reading package lists... Done
Building dependency tree       
Reading state information... Done
E: Unable to locate package vim
  • 配置default-time_zone = ‘+8:00’,不然当datetime类型的默认值为CURRENT_TIMESTAMP时,插入的时间会比系统时间少了8个小时

(4)创建用户并授权,用于在主从库之间同步数据。

进入mysql内部

mysql -uroot -proot

创建同步用户

CREATE USER 'slave'@'%' IDENTIFIED WITH mysql_native_password BY 'slave';

授予权限

GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'slave'@'%';

备注

如果本地要用客户端比如Navicat Premium连接,最好设置root的认证方式

ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'root';

否则连接可能会报错

2059 - authentication plugin 'caching_sha2_password' cannot be loaded...

(5)查看关键信息

查看两个关键信息,File和Position字段的值后面将会用到,在后面的操作完成之前,需要保证Master库不能做任何操作,否则将会引起状态变化,File和Position字段的值变化。

show master status;

shardingsphere之sharding-jdbc读写分离学习笔记_第5张图片(6)重启容器

退出mysql

exit

退出容器

exit

重启容器

docker restart master_3307

至此,master数据库安装配置完成。

slave数据库安装配置

(1)查看master的容器ip,后面设置会用到

docker inspect --format='{{.NetworkSettings.IPAddress}}' master_3307

在这里插入图片描述(2)运行容器

docker run -itd --name slave0_3308 -p 3308:3306 -e MYSQL_ROOT_PASSWORD=root mysql:8.0.20

(3)进入容器内部

docker exec -it slave0_3308 bash

(4)安装vim

apt-get update
apt-get install vim

(5)修改配置文件

vi /etc/mysql/my.cnf

(6)新增信息

[mysqld]
## 设置server_id,注意要唯一
server-id=1001  
## 开启二进制日志功能,以备Slave作为其它Slave的Master时使用
log-bin=mysql-slave-bin   
## relay_log配置中继日志
relay_log=edu-mysql-relay-bin
## 永久设置时区时间
default-time_zone = '+8:00' 

(7)设置主从信息

进入mysql内部

mysql -uroot -proot

重要提示:

如果本地要用客户端比如Navicat Premium连接,最好设置root的认证方式

ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'root';

设置主从信息

change master to master_host='172.17.0.2', master_user='slave', master_password='slave', master_port=3306, master_log_file='mysql-bin.000001', master_log_pos= 3174, master_connect_retry=30,get_master_public_key=1;

参数介绍

  • master_host :Master的地址,指的是容器的独立ip,可以通过下面命令来查看
docker inspect --format='{{.NetworkSettings.IPAddress}}' 容器名称|容器id查询容器的ip
  • master_port:Master的端口号,指的是容器的端口号
  • master_user:用于数据同步的用户
  • master_password:用于同步的用户的密码
  • master_log_file:指定 Slave 从哪个日志文件开始复制数据,即上文中提到的 File 字段的值
  • master_log_pos:从哪个 Position 开始读,即上文中提到的 Position 字段的值
  • master_connect_retry:如果连接失败,重试的时间间隔,单位是秒,默认是60秒

备注

之所以设置get_master_public_key=1,是因为开启主从复制的时候可能会报出下方的异常。原因是mysql8默认使用插件caching_sha2_password,有些client连接报这个错误,需要拿到server的public key来加密password。

 ERROR 2061 (HY000): Authentication plugin 'caching_sha2_password' reported error: Authentication requires secure connection. 

(7)查看主从同步状态

show slave status \G;

正常情况下,SlaveIORunning 和 SlaveSQLRunning 都是No,因为我们还没有开启主从复制过程。
shardingsphere之sharding-jdbc读写分离学习笔记_第6张图片

(8)开启主从复制

start slave;

(9)再次查询主从同步状态

show slave status \G;

shardingsphere之sharding-jdbc读写分离学习笔记_第7张图片
SlaveIORunning 和 SlaveSQLRunning 都是Yes,说明主从复制已经开启。

(10)重启容器

退出mysql

exit

退出容器

exit

重启容器

docker restart slave0_3308

剩余的slave1_3309,slave2_3310的安装配置方法和salve0_3308的方法是一样的,区别就是cnf文件中的server-id不能相同罢了。

出现异常参考

(1)使用start slave开启主从复制过程后,如果SlaveIORunning一直是Connecting,则说明主从复制一直处于连接状态,这种情况一般是下面几种原因造成的,我们可以根据 Last_IO_Error提示予以排除。

  • 网络不通
    解决方法:检查ip,端口
  • 密码不对
    检查是否创建用于同步的用户和用户密码是否正确
  • pos不对
    检查Master的 Position

(2)如果开启主从复制失败,需要修改和重新启动,可参考下方提示

停止主从复制

stop slave;

重置之前的配置信息

reset slave;

修改信息

change master to ......

启动主从复制

 start slave; 

备注

如果不重置修改后直接启动可能会报出下方的异常

ERROR 1872 (HY000): Slave failed to initialize relay log info structure from the repository

开放阿里云安全组端口

到阿里云安全组设置那里开放3307,3308,3309,3310端口,无需开放防火墙firewall端口。

使用客户端连接

shardingsphere之sharding-jdbc读写分离学习笔记_第8张图片

小插曲

第一次安装好之后开放了安全组端口,可是使用客户端输入公网ip和账号密码死活连接不上,这个也困扰了我很久。后面过了段时间发现设置的安全组规则不见了,直接重新设置就可以连接了,难以费解。

简单测试

在master库创建一个test数据库,如果从库也有则代表安装成功。至此主从复制的工作已经完成,接下来就是代码层面的读写分离了。

数据库初始化

在master库新建一张area表用来测试

CREATE TABLE `test`.`area`  (
  `id` bigint(0) UNSIGNED NOT NULL COMMENT 'id',
  `area_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '地区编号',
  `area_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '地区名称',
  `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '地区表(单库单表)' ROW_FORMAT = Dynamic;

编写代码

(1)pom.xml中加入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.9.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.sharding-jdbc.write-read</groupId>
	<artifactId>sharding-jdbc-write-read</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>
	<name>sharding-jdbc-write-read</name>
	<description>sharding-jdbc-write-read</description>

	<properties>
		<java.version>1.8</java.version>
		<mybatis-plus-generator.version>3.1.2</mybatis-plus-generator.version>
        <mybatis-plus.version>3.1.2</mybatis-plus.version>
        <mybatis-plus-extension.version>3.1.2</mybatis-plus-extension.version>
        <mybatis-plus-boot-starter.version>3.1.2</mybatis-plus-boot-starter.version>
        <mybatis-ehcache.version>1.0.0</mybatis-ehcache.version>
        <org.apache.velocity.version>2.1</org.apache.velocity.version>
        <druid-spring-boot-starter.version>1.1.18</druid-spring-boot-starter.version>
        <hutool-core.version>4.6.4</hutool-core.version>
        <sharding-jdbc-spring-boot-starter.version>4.0.0-RC1</sharding-jdbc-spring-boot-starter.version>
		<sharding-transaction-xa-core.version>4.0.1</sharding-transaction-xa-core.version>
		<sharding-jdbc-orchestration-spring-boot-starter.version>4.0.0-RC1</sharding-jdbc-orchestration-spring-boot-starter.version>
		<sharding-orchestration-reg-zookeeper-curator.version>4.0.0-RC1</sharding-orchestration-reg-zookeeper-curator.version>
	</properties>

	<dependencies>
		<!-- springboot基本依赖 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!--mybatis-plus依赖 -->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-generator</artifactId>
			<version>${mybatis-plus-generator.version}</version>
		</dependency>
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>${mybatis-plus-boot-starter.version}</version>
		</dependency>
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-extension</artifactId>
			<version>${mybatis-plus-extension.version}</version>
		</dependency>

		<!-- 集成echcache -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis-ehcache</artifactId>
			<version>${mybatis-ehcache.version}</version>
		</dependency>
		<dependency>
			<groupId>net.sf.ehcache</groupId>
			<artifactId>ehcache</artifactId>
		</dependency>
		
		<!-- 代码生成器模板 -->
		<dependency>
			<groupId>org.apache.velocity</groupId>
			<artifactId>velocity-engine-core</artifactId>
			<version>${org.apache.velocity.version}</version>
			<scope>test</scope>
		</dependency>

		<!-- lombok -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>

		<!-- 熱部署 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<optional>true</optional> <!-- 这个需要为 true 热部署才有效 -->
		</dependency>

		 <!-- druid数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid-spring-boot-starter.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

		<!-- bean工具 -->
		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-core</artifactId>
			<version>${hutool-core.version}</version>
		</dependency>

		<!-- 使用@ConfigurationProperties注解 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<optional>true</optional>
		</dependency>
		
		<!--shardingsphere依赖-->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>${sharding-jdbc-spring-boot-starter.version}</version>
        </dependency>
        <!-- 使用XA事务时,需要引入此依赖, 4.1.x发布 -->
		<!-- <dependency>
		    <groupId>org.apache.shardingsphere</groupId>
		    <artifactId>sharding-transaction-xa-core</artifactId>
		    <version>${sharding-transaction-xa-core.version}</version>
		</dependency> -->
		<!-- 使用编排治理 -->
		<dependency>
		    <groupId>org.apache.shardingsphere</groupId>
		    <artifactId>sharding-jdbc-orchestration-spring-boot-starter</artifactId>
		    <version>${sharding-jdbc-orchestration-spring-boot-starter.version}</version>
		</dependency>
		<!-- 引入zookeeper注册中心依赖 -->
		<dependency>
		    <groupId>org.apache.shardingsphere</groupId>
		    <artifactId>sharding-orchestration-reg-zookeeper-curator</artifactId>
		    <version>${sharding-orchestration-reg-zookeeper-curator.version}</version>
		</dependency>
		
		
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

(2)编写yml文件

spring:
  shardingsphere:
    datasource:           #数据源配置信息
      names: master,slave0,slave1,slave2
      master:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3307/test?characterEncoding=utf-8
        username: root
        password: root
      slave0:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3308/test?characterEncoding=utf-8
        username: root
        password: root
      slave1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3309/test?characterEncoding=utf-8
        username: root
        password: root
      slave2:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3310/test?characterEncoding=utf-8
        username: root
        password: root
        
    masterslave:        #主从关系配置  
      name: ms
      master-data-source-name: master
      slave-data-source-names: slave0,slave1,slave2
    props:
      sql: 
        show: true   

(3)基础CRUD代码生成

在示例代码的/src/test/java/com/project/generator/MybatisGenerator.java中简单配置运行即可。

(4)编写测试代码

@RestController
@RequestMapping("/area")
public class AreaController {

    @Autowired
    private IAreaService areaService;

    @PostMapping("/save")
    public ResponseData<?> save() {
        List<Area> list = new ArrayList<Area>();
        list.add(new Area("110000", "北京市"));
        list.add(new Area("110101", "东城区"));
        list.add(new Area("110102", "西城区"));
        list.add(new Area("110106", "丰台区"));
        areaService.saveBatch(list);
        return ResponseData.out(CodeEnum.SUCCESS, null);
    }

    @GetMapping("/list")
    public ResponseData<?> list() {
        List<Area> aList = areaService.list();
        return ResponseData.out(CodeEnum.SUCCESS, aList);
    }

    @GetMapping("/update")
    public ResponseData<?> update(Long id) {
        Area area = new Area("1180000", "天津市");
        area.setId(id);
        areaService.updateById(area);
        return ResponseData.out(CodeEnum.SUCCESS, null);
    }

    @GetMapping("/delete")
    public ResponseData<?> delete(Long id) {
        areaService.removeById(id);
        return ResponseData.out(CodeEnum.SUCCESS, null);
    }
}

(5)运行测试

  • 在执行插入操作时,会选择master库,控制台会输出
[2020-05-06 15:34:18.577] [http-nio-1000-exec-1] [INFO ] ShardingSphere-SQL - Rule Type: master-slave
[2020-05-06 15:34:18.596] [http-nio-1000-exec-1] [INFO ] ShardingSphere-SQL - SQL: INSERT INTO area  ( id,
area_code,
area_name )  VALUES  ( ?,
?,
? ) ::: DataSources: master
  • 在执行查询的时候,会随机选择一个slave库,控制台输出
[2020-05-06 15:37:40.021] [http-nio-1000-exec-3] [INFO ] ShardingSphere-SQL - Rule Type: master-slave
[2020-05-06 15:37:40.021] [http-nio-1000-exec-3] [INFO ] ShardingSphere-SQL - SQL: SELECT  id,area_code,area_name,create_time,update_time  FROM area ::: DataSources: slave0

出现异常参考

当应用启动的时候可能会报错

Description:

The bean 'dataSource', defined in class path resource [org/apache/shardingsphere/shardingjdbc/orchestration/spring/boot/OrchestrationSpringBootConfiguration.class], could not be registered. A bean with that name has already been defined in class path resource [com/project/commom/config/DruidConfig.class] and overriding is disabled.

Action:

Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

出现原因

sharding-jdbc里面也定义了数据源,很容易跟Druid数据源冲突。

解决方法

在yml中设置bean允许被覆盖。

spring:
  main:
    allow-bean-definition-overriding: true

或者修改框架的版本,目前下面这个版本组合是正常的。

mybatis-plus-boot-starter 3.1.0
sharding-jdbc-spring-boot-starter3.1.0 
spring-boot 2.0.6.RELEASE

结语

官网的文档比较详细和社区都是很活跃的,这些可以减少我们的学习成本,快速用于项目。如果在学习的过程中遇到问题可以多看看官方文档或者直接到github上面提issues,官方人员会很快给予答复的。

相关链接

Docker简介
CentOS7 安装Docker
CentOS系统Docker安装
Docker 安装 MySQL
基于Docker的Mysql主从复制搭建
MYSQL主从复制原理
连接 MySQL 报错:2059
启动slave时报错Slave failed to initialize relay log info structure from the repository
演示代码地址
shardingsphere官网地址

你可能感兴趣的:(shardingsphere)