欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Springboot + Mysql8实现读写分离

程序员文章站 2022-05-15 10:29:56
在实际的生产环境中,为了确保数据库的稳定性,我们一般会给数据库配置双机热备机制,这样在master数据库崩溃后,slave数据库可以立即切换成主数据库,通过主从复制的方式将数据从主库同步至从库,在业务代码中编写代码实现读写分离(让主数据库处理 事务性增、改、删操作,而从数据库处理查询操作)来提升数据 ......

在实际的生产环境中,为了确保数据库的稳定性,我们一般会给数据库配置双机热备机制,这样在master数据库崩溃后,slave数据库可以立即切换成主数据库,通过主从复制的方式将数据从主库同步至从库,在业务代码中编写代码实现读写分离(让主数据库处理 事务性增、改、删操作,而从数据库处理查询操作)来提升数据库的并发负载能力。
Springboot + Mysql8实现读写分离

下面我们使用最新版本的mysql数据库(8.0.16)结合springboot实现这一完整步骤(一主一从)。

安装配置mysql

  • https://dev.mysql.com/downloads/mysql/页面下载mysql安装包,我这里下载的是mysql8.0.16 linux-generic.

  • 准备两台虚拟机用作安装mysql,并将下载后的文件mysql-8.0.16-linux-glibc2.12-x86_64.tar.xz上传至服务器/app/mysql
    • 192.168.249.131 centos7 主
    • 192.168.249.129 centos7 从
  • 查看防火墙状态,如果启动需要先关闭防火墙
service firewalld status  ## 查看防火墙状态
service firewalld stop   ## 关闭防火墙
  • 使用如下命令将xz文件解压成tar文件
    xz -d mysql-8.0.16-linux-glibc2.12-x86_64.tar.xz

  • 解压安装包
    tar -xvf mysql-8.0.16-linux-gl-ibc2.12-x86_64.tar

  • 在/app/mysql下建立data文件夹,用于存放数据

  • 创建mysql用户组和mysql用户
groupadd mysql                                  ## 创建用户组
useradd -g mysql -d /app/mysql mysql    ## 在用户组下创建mysql用户并授权相关目录
groupdel mysql                                  ## 删除用户组名(若报已存在相关用户组)
userdel mysql                                   ## 删除用户(若报已存在相关用户)
  • 初始化安装mysql数据库
    ./mysql-8.0.16-linux-glibc2.12-x86_64/bin/mysqld --user=mysql --basedir=/app/mysql --datadir=/app/mysql/data --initialize

    2019-07-01t02:05:52.681626z 0 [warning] [my-011070] [server] 'disabling symbolic links using --skip-symbolic-links (or equivalent) is the default. consider not using this option as it' is deprecated and will be removed in a future release.
    2019-07-01t02:05:52.681694z 0 [system] [my-013169] [server] /app/mysql/mysql-8.0.16-linux-glibc2.12-x86_64/bin/mysqld (mysqld 8.0.16) initializing of server in progress as process 1479
    2019-07-01t02:05:52.681726z 0 [error] [my-010338] [server] can't find error-message file '/app/mysql/share/errmsg.sys'. check error-message file location and 'lc-messages-dir' configuration directive.
    2019-07-01t02:05:55.713747z 5 [note] [my-010454] [server] a temporary password is generated for root@localhost: xa6(h>rk/r<e
    2019-07-01t02:05:57.303240z 0 [system] [my-013170] [server] /app/mysql/mysql-8.0.16-linux-glibc2.12-x86_64/bin/mysqld (mysqld 8.0.16) initializing of server has completed

    注意,此时mysql会生成一个默认的临时密码,如上所示,需要先保存下来然后修改

  • 建立mysql服务并增加执行权限
    cp mysql-8.0.16-linux-glibc2.12-x86_64/support-files/mysql.server /etc/init.d/mysqld

  • 修改mysql配置文件 vi /etc/my.cnf 增加如下配置

    [mysqld]
    port=3306
    basedir=/app/mysql/mysql-8.0.16-linux-glibc2.12-x86_64
    datadir=/app/mysql/data
    socket=/tmp/mysql.sock
    symbolic-links=0
    
    [mysqld_safe]
    log-error=/app/mysql/data/log/error.log
    pid-file=/app/mysql/data/mysql.pid
    user=mysql
    tmpdir=/tmp
    character_set_server=utf8
    default-storage-engine=innodb
    init_connect='set names utf8'
    
    !includedir /etc/my.cnf.d

    如果报日志权限相关错误,请先建立对应日志文件,并给mysql用户授权
    chown -r mysql:mysql /app/mysql/data/log/error.log

  • 启动mysql服务
    service mysqld start

  • 建立mysql客户端软连接
    ln -s /app/mysql/mysql-8.0.16-linux-glibc2.12-x86_64/bin/mysql /usr/local/bin/mysql

  • 登录mysql修改密码
mysql -uroot -p密码       ## 登录 
alter user 'root'@'localhost' identified with mysql_native_password by '000000';
  • 设置远程登录
use mysql;
update user set host='%' where user='root' limit 1;
flush privileges;

配置mysql主从同步(binlog)

复制原理

  • master将数据改变记录到二进制日志(binary log)中,也就是配置文件log-bin指定的文件,这些记录叫做二进制日志事件(binary log events) 
  • slave通过i/o线程读取master中的binary log events并写入到它的中继日志(relay log) 
  • slave重做中继日志中的事件,把中继日志中的事件信息一条一条的在本地执行一次,完成数据在本地的存储,从而实现将改变反映到它自己的数据(数据重放)

复制要求

  • 主从服务器操作系统版本和位数一致 
  • master和slave数据库的版本要一致 
  • master和slave数据库中的数据要一致 
  • master开启二进制日志,master和slave的server_id在局域网内必须唯一

配置步骤

主数据库(192.168.249.131)

  • 创建同步用户并授权
create user 'slave'@'192.168.249.129' identified with 'mysql_native_password' by '000000';
grant replication slave, replication client on *.* to 'slave'@'192.168.249.129';
flush privileges;

注意这里创建用户时需要选用mysql_native_password加密方式插件,否则默认会使用caching_sha2_password加密方式,这样在同步的时候需要使用ssl的身份进行验证,为了方便简单,我们直接采用mysql_native_password方式

  • 修改配置/etc/my.cnf,新增如下配置,开启binlog,并重启mysql服务
    [mysqld] # 开启二进制日志功能 log-bin=mysql-bin # 设置server_id,,注意在网段内要唯一 server-id=131 #(可选配置)要同步的数据库名,要同步多个数据库,就多加几个replicate-db-db=数据库名 binlog-do-db=mydb #(可选配置)要忽略的数据库 binlog-ignore-db=mysql

  • 查看主服务器状态
    show master status
    Springboot + Mysql8实现读写分离

注意看里面的参数,特别前面两个file和position,在从服务器(slave)配置主从关系会有用到的。

从数据库(192.168.249.129)

  • 修改/etc/my.cnf,新增如下配置,并重启服务
[mysqld]
server-id=129
log-bin=mysql-bin
replicate-do-db=mydb
replicate-ignore-db=mysql
  • 在slave中设置master信息,指定同步位置
stop slave;
change master to master_host='192.168.249.131',master_user='slave',master_password='000000',master_log_file='mysql-bin.000001',master_log_pos=155;
start slave;

参数说明:
master_host='192.168.249.131' ## master的ip地址
master_user='slave' ## 用于同步数据的用户(在master中授权的用户)
master_password='000000' ## 同步数据用户的密码
master_port=3306 ## master数据库服务的端口
masterlogfile='mysql-bin.000001' ##指定slave从哪个日志文件开始读复制数据(master上执行命令的结果的file字段)
masterlogpos=155 ## 从哪个position号开始读(master上执行命令的结果的position字段)
masterconnectretry=30 ##当重新建立主从连接时,如果连接建立失败,间隔多久后重试。单位为秒,默认设置为60秒,同步延迟调优参数。

  • 查看从服务器状态
    show slave status\g;

    Springboot + Mysql8实现读写分离

至此数据库层面主从配置完成。

springboot中配置主从读写分离

在主从模式下请遵守如下规则:
主数据库 只执行 insert,update,delete 操作
从数据库 只执行select操作

我们这里使用开源项目[dynamic-datasource-spring-boot-starter](https://gitee.com/baomidou/dynamic-datasource-spring-boot-starter/wikis/)作为读写分离的工具包

使用方法

  • 在mydb主数据库中建立一个简单数据表user,建好后从数据库会自动同步
drop table if exists `user`;
create table `user` (
`id` int(11) not null auto_increment,
`account` varchar(255) collate utf8mb4_unicode_ci default null,
`name` varchar(255) collate utf8mb4_unicode_ci default null,
`position` varchar(255) collate utf8mb4_unicode_ci default null,
primary key (`id`)
) engine=innodb auto_increment=5 default charset=utf8mb4 collate=utf8mb4_unicode_ci;
  • 引入相关依赖
<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.mybatis.spring.boot</groupid>
           <artifactid>mybatis-spring-boot-starter</artifactid>
           <version>2.0.1</version>
       </dependency>

       <dependency>
           <groupid>com.baomidou</groupid>
           <artifactid>dynamic-datasource-spring-boot-starter</artifactid>
           <version>2.5.5</version>
       </dependency>

       <dependency>
           <groupid>mysql</groupid>
           <artifactid>mysql-connector-java</artifactid>
           <version>8.0.15</version>
       </dependency>

       <dependency>
           <groupid>org.projectlombok</groupid>
           <artifactid>lombok</artifactid>
           <optional>true</optional>
       </dependency>

       <dependency>
           <groupid>org.springframework.boot</groupid>
           <artifactid>spring-boot-starter-test</artifactid>
           <scope>test</scope>
       </dependency>

   </dependencies>
  • 配置数据源
spring:
  datasource:
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      strict: false #设置严格模式,默认false不启动. 启动后再为匹配到指定数据源时候回抛出异常,不启动会使用默认数据源.
      datasource:
        master:
          type: com.zaxxer.hikari.hikaridatasource
          url: jdbc:mysql://192.168.249.131:3306/mydb?characterencoding=utf8&zerodatetimebehavior=converttonull&usessl=false
          username: root
          password: '000000'
          driver-class-name: com.mysql.cj.jdbc.driver
        slave_1:
          type: com.zaxxer.hikari.hikaridatasource
          url: jdbc:mysql://192.168.249.129:3306/mydb?characterencoding=utf8&zerodatetimebehavior=converttonull&usessl=false
          username: root
          password: '000000'
          driver-class-name: com.mysql.cj.jdbc.driver
  • 在启动类入口加入mybatis扫描包
@springbootapplication@mapperscan("com.jianzh5.dynamic.mapper")
public class dynamicdatsourcebootstrap {    
    public static void main(string[] args) {        
        springapplication.run(dynamicdatsourcebootstrap.class, args);
    }
}
  • 建立实体类user
@data
public class user {
    private int id;
    private string account;
    private string name;
    private string position;
}
  • 建立mapper接口文件,新增两个方法adduser(user user),getbyid(int id)
public interface userdao {
    @insert("insert into user(account, name, position) values(#{account}, #{name}, #{position})")
    @options(usegeneratedkeys = true,keyproperty = "id")
    int adduser(user user);

    @select("select * from user where id = #{id}")
    user getbyid(int id);
}
  • 建立service服务层相关实现
public interface userservice {
        int adduser(user user);
        user getbyid(int id);
}
@service
public class userserviceimpl implements userservice {
        @resource
        private userdao userdao;

        @override
        public int adduser(user user) {
            return userdao.adduser(user);
        }
        @ds("slave")
        @override
        public user getbyid(int id) {
            return userdao.getbyid(id);
        }
}

由于在数据源中配置了primary: master,默认操作都会从主库执行,使用注解@ds切换数据源,此注解也可直接用于类文件上,同时存在方法注解优先于类上注解。

  • 编写单元测试进行测试

    public class userservicetest extends dynamicdatsourcebootstraptests {
        @autowired
        private userservice userservice;
        @test
        public void testadduser(){
            user user = new user();
            user.setname("李四");
            user.setaccount("sili");
            user.setposition("java开发工程师");
            int i = userservice.adduser(user);
            system.out.println(user);
        }
        @test
        public void testgetbyid(){
            int id = 4;
            user user = userservice.getbyid(id);
            assert.assertequals("sanzhang",user.getaccount());
        }
    }
  • 通过观察执行日志,发现读写数据库会根据@ds注解进行切换,至此springboot集成数据库主从读写分离完成。

请关注个人公众号:java日知录