使用docker搭建mysql 的一主双从模式,并使用springboot+mybatis实现动态数据源切换,从而实现简单的读写分离(解决mybatis集成多数据源会遇到的bug)

1. docker

docker的安装有很多方式,可以参考:

https://www.runoob.com/docker/centos-docker-install.html

还需要配置镜像加速

2. docker下安装mysql

打开docker hub,然后搜索mysql ,我们安装5.7 版本的mysql

docker pull mysql:5.7

3.做一些启动前的准备

我这里就直接在一台机器上启动3个mysql容器了,就不分3台机器了。

主要是将容器中的mysql 存放数据的文件夹映射到主机上,否则,容器一删除,数据就全没了。

在主机上添加为3个数据库分别添加3个文件夹:

mkdir /usr/local/docker-mysql-data
mkdir /usr/local/docker-mysql-data/master
mkdir /usr/local/docker-mysql-data/slaver1
mkdir /usr/local/docker-mysql-data/slaver2

4. 启动mysql容器

启动master:

docker run -p 3339:3306 --name master -v /usr/local/docker-mysql-data/master:/var/lib/mysql  -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7

启动两个slaver:

docker run -p 3340:3306 --name slaver1 -v /usr/local/docker-mysql-data/slaver1:/var/lib/mysql  -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7

docker run -p 3341:3306 --name slaver2 -v /usr/local/docker-mysql-data/slaver2:/var/lib/mysql  -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7

参数说明:

都拿master进行举例说明

  • -p 映射端口:将容器中的3306端口映射到主机的3339端口,供外部访问
  • --name : 给容器取的名字,可以自定义,越容易记越好,之后对容器进行操作时可直接使用名字,而不用使用容器id了
  • -v: 将容器中的文件夹挂载到主机的文件夹上,这样的话,当容器被删除,数据也能够保存下来
  • -e: 外部参数,MYSQL_ROOT_PASSWORD=123456的意思是给root用户设置密码为123456,且通过命令启动容器后会自动为root用户赋予所有的权限,不需要手动赋予权限
  • -d :表示让容器后台运行
  • mysql:5.7  指定运行的mysql的版本,我们只下载了5.7版本的镜像,所以只能指定5.7版本

5. 使用nacicat进行连接3台数据库

在连接前需要开放端口:

  • 如果使用的云服务器,那么去开放安全组的端口
  • 开放linux端口,iptables -A INPUT -ptcp --dport 端口号 -j ACCEPT

然后开始连接,拿master举例:

端口号使用映射的那个主机端口号即可,账号为root,密码是刚刚设置的12456

6. 配置Master(主)

通过命令进入到Master容器内部:

docker exec -it master /bin/bash

进入容器后,切换到/etc/mysql目录下

cd /etc/mysql

然后使用命令对my.cnf进行编辑

vi my.cnf

此时会报出bash: vi: command not found,需要我们在docker容器内部自行安装vim。一次执行以下命令即可安装成功

apt-get update
apt-get install vim

然后再次使用vim编辑my.cnf,在my.cnf中添加如下配置:

[mysqld]
## 同一局域网内注意要唯一,所以设置为主机的mysql 端口地址就可以了
server-id=3339
## 开启二进制日志功能,可以随便取(关键)
log-bin=mysql-bin

配置完成之后,需要重启master容器

#先退出容器内部
exit
#重启master容器
docker restart master

在Master数据库创建数据同步用户,授予用户 slave REPLICATION SLAVE权限和REPLICATION CLIENT权限,用于在主从库之间同步数据。做法:使用navicat连接master数据库,然后新建一个查询,输入语句:

CREATE USER 'slave'@'%' IDENTIFIED BY '123456';

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

至此,master数据库配置完成

7. 配置slaver(从)

和配置Master(主)一样,在Slave配置文件my.cnf中添加如下配置:

[mysqld]
## 设置server_id,注意要唯一
server-id=3340 
## 开启二进制日志功能,以备Slave作为其它Slave的Master时使用
log-bin=mysql-slave-bin   
## relay_log配置中继日志
relay_log=edu-mysql-relay-bin  

配置完成后也需要重启slaver1,slaver2容器。

8. 链接Master(主)和Slave(从)

8.1 查询master的一些状态

使用navicat连接master数据库,并输入查询语句:

show master status;

结果:

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

8.2 操作从数据库

使用navicat连接两个slaver数据库,并执行以下sql语句:

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

命令说明:

master_host :Master的地址,指的是容器的独立ip,可以通过docker inspect --format='{{.NetworkSettings.IPAddress}}' 容器名称|容器id查询容器的ip

master_port:Master的端口号,指的是容器的端口号

master_user:用于数据同步的用户,即我们上面在master数据库中创建的slaver用户,该用户可以同步主从数据库数据

master_password:用于同步的用户的密码

master_log_file:指定 Slave 从哪个日志文件开始复制数据,即上文中提到的 File 字段的值

master_log_pos:从哪个 Position 开始读,即上文中提到的 Position 字段的值

master_connect_retry:如果连接失败,重试的时间间隔,单位是秒,默认是60秒

8.3 查看slaver状态

在slaver终端上输入sql语句

show slave status

 结果:slave-io-running 和slave-sql-running 都为NO

 正常情况下,SlaveIORunning 和 SlaveSQLRunning 都是No,因为我们还没有开启主从复制过程。

8.4 开启主从复制

在slaver数据库上运行

start slave

然后再次查询主从同步状态

会发现SlaveIORunning 和 SlaveSQLRunning 变为了YES,即表示主从复制链接成功

 

9. 测试主从复制

只需要在master上添加一个数据库,然后查看两个从数据库是否也存在,如果存在,那么说明复制成功

10. 其他 

10.1 停止主从复制

如果你不想从数据库去监听主数据库的变化了,那么就需要停止主从复制

在slaver数据库上执行sql

stop slave;

如果你想重置slaver数据库读取主数据变化数据的位置(slaver都是通过读取master的二进制文件来进行数据库的数据变更)到主数据库最原始的状态,那么使用以下sql命令:

reset slave;

但是使用了这个指令后,一般都会导致 slaver重新连接master进行主从复制 失败,只能重新关联以下master。

先停止以下slaver的主从复制并且重置

stop slave;
reset slave;

再去master数据库中查看下状态,即file和position:

show master status;

 然后再次再slaver中进行关联:

change master to master_host='172.17.0.2', master_user='slave', master_password='123456', master_port=3306, master_log_file='mysql-bin.000002', master_log_pos= 344, master_connect_retry=30;

最后重新启动slaver的主从复制

start slave

查看slaver状态

show slave status 

发现SlaveIORunning 和 SlaveSQLRunning 又变为了YES,说明成功了。

但是 你在停止主从复制这个时间段的数据已经同步不过来了。

如果你还是想同步过来,那你只能去master中的二进制文件中查看上次停止执行的指针位置,然后重新关联主从时指定一下

11 spring boot+mybatis实现动态数据源 

现在我们已经有一主双从的数据库了,那么接下来就需要实现读写分离了。怎么实现呢?我们先用简单的动态数据源切换的方式来做

源码位置:https://gitee.com/tfp-study/dynamic-data-source

我只做简单的一些说明:

结构大致是这样的,说明一下config包中的类:

  • MapperConfig:这个类中需要定义3个数据源(一主双从),sqlSession等相关bean
  • DynamicDataSource: 这个类主要是继承AbstractRoutingDataSource类,重写determineCurrentLookupKey方法,通过这个方法来指定某次的数据库操作需要使用哪个数据源(调用ThreadLocalDataSource中的方法获取)
  • ThreadLocalDataSource: 通过ThreadLocal来存储某次数据库操作需要使用的数据源

当我们需要切换数据源时:

通过向ThreadLocal中设置值来决定使用哪个数据源。

集成时遇到的坑:

问题:

一开始我在application.yml中的配置:

mybatis:
  mapper-locations: mybatis/mappers/UserMapper.xml
  type-aliases-package: cn.tanfp.dynamicdatasource.pojo
  configuration:
    map-underscore-to-camel-case: true
    call-setters-on-nulls: true

 并且我的mapper.xml是放在resource文件夹中的。

然后我每次访问接口都会报错:

 Invalid bound statement (not found): cn.tanfp.dynamicdatasource.mapper.UserMapper.xxxxxx。

分析:

我一开始以为可能是xml或扫描包什么的路径有问题,结果核对了一遍,发现并没有错,并且target中class文件中,也存在xml文件。然后我就很疑惑,通过查看源码,终于发现了原因:

首先看下源码:

通过源码就能发现,由于我们在MapperConfig类中,创建了3个DataSource Bean,导致Mybatis的自动配置类没有加载成功,从而使得application.yml中有关mybatis的配置属性全部失效了。

解决办法:

在我们自己写的MapperConfig中,通过设置sqlSessionFactoryBean对象的属性来指定

@Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean() throws IOException {
        SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource());
        // 设置mapper.xml扫描
        // (一定不能在application.yml中配置,因为我们现在是用的自己的mybatis配置,application.yml中的配置是失效状态,要配置属性,只能在这个配置类中配置)
        sqlSessionFactoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver().
                        getResources("classpath:mybatis/mappers/*.xml"));
        // 设置返回值pojo的别名包
        sqlSessionFactoryBean.setTypeAliasesPackage("cn.tanfp.dynamicdatasource.pojo");
        return sqlSessionFactoryBean;
    }

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巴中第一皇子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值