zoukankan      html  css  js  c++  java
  • springboot多数据源配合docker部署mysql主从实现读写分离

    本篇主要有两部分:

    • 1、使用docker部署mysql主从 实现主从复制

    • 2、springboot项目多数据源配置,实现读写分离

    一、使用docker部署mysql主从 实现主从复制

    此次使用的是windows版本docker,mysql版本是5.7

    • 1、使用docker获取mysql镜像

    docker pull mysql:5.7.23 #拉取镜像文件

    docker images #查看镜像文件
    查看镜像文件

    • 2、使用docker运行mysql master

    docker run --name mysql-master --privileged=true -v F:dockerVmysql:/var/lib/mysql -p 3307:3306 -e MYSQL_ROOT_PASSWORD=654321 -d mysql:5.7.23

      • --name 容器名称mysql-master

      • --privileged 指定了当前容器是否真正的具有root权限,所谓的root权限是指具有宿主机的root权限,而不仅仅只是在容器内部有root权限

      • -v 将系统的F:dockerVmysql挂载到容器的/var/lib/mysql,注意是将宿主机 挂载到 容器内部,而不是将容器内部挂载到宿主机

      • -p 表示宿主机上的某个端口映射到docker容器内的某个端口,这里也就是将宿主机的3307端口映射到容器内部的3306端口

      • -e 表示指定当前容器运行的环境变量,该变量一般在容器内部程序的配置文件中使用,而在外部运行容器指定该参数。这里的MYSQL_ROOT_PASSWORD表示容器内部的MySQL的启动密码

      • -d 后台运行,镜像文件为mysql:5.7.23

    • 接下来进入容器内部,修改配置,使其作为mysql master运行

    docker exec -it mysql-master bash #进入容器内部

    进入容器内部

    • 配置mysql master,修改mysql.cnf

    修改mysql.cnf

    • 使用vim修改mysql.cnf,没有安装vim会提示bash: vi: command not found 则需要安装vim

    apt-get install vim

    apt-get update

    apt-get install vim

    vim mysqld.cnf #修改cnf文件,添加 server-id 表示master服务标识,同一局域网内注意要唯一 和 log-bin=mysql-bin 开启二进制日志功能,可以随便取,用来完成主从复制

    修改cnf文件

    • 修改完成mysql的配置后,需要重启服务生效

    service MySQL restart # 重启mysql服务时会使得docker容器停止,我们还需要docker start mysql-master启动容器

    docker start mysql-master #启动容器

    • 连接mysql客户端,创建用来完成主从复制的账号

    mysql -uroot -p654321 #连接mysql

    连接mysql

    • 为从服务器创建一个可以用来操作master服务器的账户,也就是创建一个专门用来复制binlog的账号,并且赋予该账号复制权限,其命令如下

    grant replication slave on *.* to 'slaveaccount'@'%' identified by '654321'; #账号slaveaccount 密码654321

    flush privileges; #刷新用户权限表

    show master status #查看mysql master的状态
    查看mysql master的状态

    记录下上的position和file 在创建MySQL slave配置时会用到

    到这里mysql master 已经配置完成了

    • 3、使用docker运行mysql slave

    docker run --name mysql-slave --privileged=true -v F:dockerVmysql-slave:/var/lib/mysql -p 3308:3306 --link mysql-master:master -e MYSQL_ROOT_PASSWORD=654321 -d mysql:5.7.23

    mysql slave 的参数主要和master有两点不同

    • 所映射的宿主机的端口号不能与master容器相同,因为其已经被master容器占用;

    • 必须加上--link参数,其后指定了当前容器所要连接的容器,mysql-master表示所要连接的容器的名称,master表示为该容器起的一个别名,通俗来讲,就是slave容器通过这两个名称都可以访问到master容器。这么做的原因在于,如果master与slave不在同一个docker network中,那么这两个容器相互之间是没法访问的。

    docker exec -it mysql-slave /bin/bash #进入mysql salve容器

    vim mysqld.cnf #修改cnf文件,添加 server-id 表示slave服务标识,如果此salve需要作为其他mysql的主,那么就需要配置log-bin=mysql-bin

    service MySQL restart # 重启mysql服务时会使得docker容器停止,我们还需要docker start mysql-slave启动容器

    docker start mysql-master #启动容器

    mysql -uroot -proot #连接mysql服务

    配置mysql slave 使其以slave模式运行
    change master to master_host='172.17.0.2', master_user='slaveaccount', master_password='654321', master_port=3306, master_log_file='mysql-bin.000001', master_log_pos=2272, master_connect_retry=30;

    注意:这一步主要在slave是配置master的信息,包括 master的 地址、端口、账号、密码、log文件、log文件偏移量、重试。下面介绍这些参数值从何获取

    • 获取master_host,使用docker inspect mysql-master查看master容器元数据。其中 IPAdress是master_host
      获取master_host

    • 获取master_port,是运行master映射容器内部的端口,这里就是3306

    • 获取master_user和master_password,是第一步中在master中创建的slave账号

    • 获取master_log_file和master_log_pos,是配置master中最后一步使用show master status获取的文件和偏移量

    • 获取master_connect_retry,是slave重试连接master动作前的休眠时间,单位s,默认60s

    start slave; #以slave模式运行

    show salve status G; #查看slave的状态

    查看slave的状态

    可以看到,Slave_IO_Running:YES和Slave_SQL_Running:YES 证明此时主从复制已经就绪,slave配置到此完成

    • 4、验证主从复制效果

      • 这里我们在连接master,并创建一张表,添加数据,在从库查看数据是否同步。
      • 在主库创建数据,并在从库查看数据是否同步成功。
    mysql> create database test;
    Query OK, 1 row affected (0.01 sec)
    
    mysql> use test;
    Database changed
    
    mysql> create table t_user(id bigint, name varchar(255));
    Query OK, 0 rows affected (0.02 sec)
    
    mysql> insert into t_user(id, name) value (1, 'cgg');
    Query OK, 1 row affected (0.01 sec)
    
    mysql> select * from test.t_user;
    +------+------+
    | id   | name |
    +------+------+
    |    1 | cgg |
    +------+------+
    1 row in set (0.00 sec)
    
    • 5、mysql主从复制原理

    mysql主从复制原理

    • 主库db的更新事件(update、insert、delete)被写到binlog
    • 主库创建一个binlog dump thread,把binlog的内容发送到从库
    • 从库启动并发起连接,连接到主库
    • 从库启动之后,创建一个I/O线程,读取主库传过来的binlog内容并写入到relay log
    • 从库启动之后,创建一个SQL线程,从relay log里面读取内容,从Exec_Master_Log_Pos位置开始执行读取到的更新事件,将更新内容写入到slave的db

    二、springboot项目多数据源配置,实现读写分离

    • 1、主从多数据源配置

      • yml配置
    server:
      port: 8888
      servlet:
        encoding:
          charset: UTF-8
          force: true
          enabled: true
    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
        url: jdbc:mysql://127.0.0.1:3307/test?serverTimezone=GMT%2B8&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&useSSL=false
        username: root
        password: 654321
    slave:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
        url: jdbc:mysql://127.0.0.1:3308/test?serverTimezone=GMT%2B8&characterEncoding=utf-8&autoReconnect=true&failOverReadOnly=false&useSSL=false
        username: root
        password: 654321
    
    • 数据源配置
    /**
     * @author cgg
     **/
    @Configuration
    @EnableTransactionManagement
    public class DynamicDataSourceConfig {
    
    
        @Bean(name = "slaveDatasource")
        @ConfigurationProperties(prefix = "slave.datasource")
        public DataSource dbSlave() {
            return DruidDataSourceBuilder.create().build();
        }
    
        @Bean(name = "dataSource")
        @ConfigurationProperties(prefix = "spring.datasource")
        public DataSource dbMaster() {
            return DruidDataSourceBuilder.create().build();
        }
    
    
        @Bean
        @Primary
        public DataSource multipleDataSource(@Qualifier("dataSource") DataSource db,
                                             @Qualifier("slaveDatasource") DataSource slaveDatasource) {
            DynamicDataSource dynamicDataSource = new DynamicDataSource();
            Map<Object, Object> targetDataSources = new HashMap<>(16);
            targetDataSources.put("dataSource", db);
            targetDataSources.put("slaveDatasource", slaveDatasource);
            dynamicDataSource.setTargetDataSources(targetDataSources);
            //设置默认数据源为从库,如果写操作业务多,可以默认设置为主库
            dynamicDataSource.setDefaultTargetDataSource(slaveDatasource);
            return dynamicDataSource;
        }
    
        @Bean("sqlSessionFactory")
        public SqlSessionFactory sqlSessionFactory() throws Exception {
            MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
            sqlSessionFactory.setDataSource(multipleDataSource(dbSlave(), dbMaster()));
            MybatisConfiguration configuration = new MybatisConfiguration();
            configuration.setJdbcTypeForNull(JdbcType.NULL);
            configuration.setMapUnderscoreToCamelCase(true);
            configuration.setCacheEnabled(false);
            sqlSessionFactory.setConfiguration(configuration);
            sqlSessionFactory.setMapperLocations((new PathMatchingResourcePatternResolver()).getResources(DEFAULT_MAPPER_LOCATION));
            PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
            paginationInterceptor.setOverflow(false);
            paginationInterceptor.setLimit(-1);
            paginationInterceptor.setCountSqlParser(tenantSqlParserCountOptimize());
            Interceptor[] plugins = new Interceptor[]{new ShardTableInterceptor(), paginationInterceptor};
            sqlSessionFactory.setPlugins(plugins);
            return sqlSessionFactory.getObject();
        }
    
    }
    
    /**
     * 动态数据源
     *
     * @author cgg
     **/
    @Slf4j
    public class DynamicDataSource  extends AbstractRoutingDataSource {
        @Override
        protected Object determineCurrentLookupKey() {
            return DbContextHolder.getDbType();
        }
    }
    
    • 2、配置切面控制主从数据源切换、读从库写主库,实现读写分离

      • aop切面
    /**
     * @author cgg
     **/
    @Component
    @Order(value = -100)
    @Slf4j
    @Aspect
    public class DataSourceSwitchAspect {
    
        //master 包下的操作都是操作主库业务
        @Pointcut("execution(* com.master..*.*(..))")
        private void db1Aspect() {
        }
    
        //slave 包下的操作都是操作从库业务
        @Pointcut("execution(* com.slave.*.*(..))")
        private void db2Aspect() {
        }
    
    
        @Before("db1Aspect()")
        public void dbMaster() {
            log.debug("切换到Master 数据源...");
            DbContextHolder.setDbType("dataSource");
        }
    
        @Before("db2Aspect()")
        public void dbSlave() {
            log.debug("切换到Slave 数据源...");
            DbContextHolder.setDbType("slaveDatasource");
        }
    }
    
    
    /**
     * @author cgg
     **/
    public class DbContextHolder {
        private static final ThreadLocal contextHolder = new ThreadLocal<>();
        /**
         * 设置数据源
         * @param dbType
         */
        public static void setDbType(String dbType) {
            contextHolder.set(dbType);
        }
    
        /**
         * 取得当前数据源
         * @return
         */
        public static String getDbType() {
            return (String) contextHolder.get();
        }
    
        /**
         * 清除上下文数据
         */
        public static void clearDbType() {
            contextHolder.remove();
        }
    }
    
    • 3、验证读写分离效果

      • 使用接口调用不同接口,切点会自动切换数据源,这里写库接口只会操作主库,读库接口会操作从库。实现读写分离
  • 相关阅读:
    闭包 (Closure)
    RSA算法
    HTTPS
    SSH
    HDU1754 I hate it_线段树(入门级别)
    HDU1166 敌兵布阵_线段树
    c++运算符优先级表
    归并排序练习.
    HDU 1969 精度二分
    uva10944 状态压缩bfs or DP
  • 原文地址:https://www.cnblogs.com/wa1l-E/p/15323059.html
Copyright © 2011-2022 走看看