阅读 189

【MySQL】docker-compose 部署主从复制

这是运维范畴的工作,但是后端开发人员也必须懂。

注:本文使用的是最新版 docker,docker 自带 compose 命令。非最新版玩家在运行时使用 docker-compose 命令就行。

配置

文件结构

为了方便和容易管理,我的目录结构是这样的,仅供参考:

master-slave/
├── docker-compose.yml
├── master
│   ├── data
│   ├── log
|   │   └── error.log
│   └── my.cnf
├── slave1
│   ├── data
│   ├── log
|   │   └── error.log
│   └── my.cnf
└── slave2
    ├── data
    ├── log
    │   └── error.log
    └── my.cnf
复制代码

根据目录结构可以看出这里采用一主双从的结构。data 目录用于存储 MySQL 数据,log/error.log 用于保存 MySQL 的异常输出,my.cnf 是 MySQL 配置文件。

Compose 文件

注意点:

  1. 必须定义网络,否则各容器无法通讯,不然从库怎么找主库?
  2. 在内部网络中,容器之间相互访问是通过容器内部的端口,不是主机端口。
  3. 数据卷 error.log 是给 MySQL 的 log-error 配置项使用的,必须先创建好,否则会报:File Not Found 错误而无法启动容器。
  4. 数据卷 error.log 由于是在宿主机创建的,容器内部的 mysql 用户无权访问会报 Permission Deny 错误而无法启动容器,因此需要配置 entrypoint 在容器启动时执行命令进行授权。
version: '3'

# 建立一个网络,否则机器之间无法进行连接
networks:
  mysql-master-slave-network:
    driver: bridge
    ipam:
      config:
        - subnet: 172.25.0.0/24

services:
  mysql-master:
    image: mysql
    container_name: mysql-master
    ports:
    - 3307:3306
    volumes:
      - "./master/data:/var/lib/mysql"
      - "./master/log/error.log:/var/log/mysql/error.log"
      - "./master/my.cnf:/etc/mysql/my.cnf"
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: test
    entrypoint: bash -c "chown -R mysql:mysql /var/log/mysql && exec /entrypoint.sh mysqld"
    restart: unless-stopped
    networks:
      mysql-master-slave-network:
        ipv4_address: 172.25.0.101
 
  mysql-slave-1:
    image: mysql
    container_name: mysql-slave-1
    ports:
    - 3308:3306
    volumes:
      - "./slave1/data:/var/lib/mysql"
      - "./slave1/log/error.log:/var/log/mysql/error.log"
      - "./slave1/my.cnf:/etc/mysql/my.cnf"
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: test
    entrypoint: bash -c "chown -R mysql:mysql /var/log/mysql && exec /entrypoint.sh mysqld"
    restart: unless-stopped
    networks:
      mysql-master-slave-network:
        ipv4_address: 172.25.0.102
 
  mysql-slave-2:
    image: mysql
    container_name: mysql-slave-2
    ports:
      - 3309:3306
    volumes:
      - "./slave2/data:/var/lib/mysql"
      - "./slave2/log/error.log:/var/log/mysql/error.log"
      - "./slave2/my.cnf:/etc/mysql/my.cnf"
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: test
    entrypoint: bash -c "chown -R mysql:mysql /var/log/mysql && exec /entrypoint.sh mysqld"
    restart: unless-stopped
    networks:
      mysql-master-slave-network:
        ipv4_address: 172.25.0.103
复制代码

my.cnf 配置

  • Master

    [mysqld]
    pid-file         = /var/run/mysqld/mysqld.pid
    socket           = /var/run/mysqld/mysqld.sock
    datadir          = /var/lib/mysql
    log-error        = /var/log/mysql/error.log
    bind-address     = 0.0.0.0
    secure-file-priv = NULL
    max_connections  = 16384
    
    character-set-server = utf8mb4
    collation-server     = utf8mb4_general_ci
    init_connect         ='SET NAMES utf8mb4'
    skip-name-resolve
    skip-host-cache
    
    server_id = 1                       # ID,不要重复
    log-bin = mysql-bin                 # 开启 bin log                 
    binlog-do-db = test                 # 开启 bin log 的数据库名,如果有多个,重复设置就行
    log-slave-updates                   # 将从服务器从主服务器收到的更新记入到从服务器自己的二进制日志文件中
    sync_binlog = 1                     # 控制 bin log 写入频率
    auto_increment_offset = 1           # 自增 offset,主主同步时用到
    auto_increment_increment = 1        # 自增间隔,同样主主同步时使用
    expire_logs_days = 7                # bin 留存天数
    log_bin_trust_function_creators = 1 # 复制定义的函数
    
    
    # Custom config should go here
    !includedir /etc/mysql/conf.d/
    复制代码
  • Slave1 & Slave2,唯一区别是 server_id23,和 Master 不一样就行。

    [mysqld]
    pid-file         = /var/run/mysqld/mysqld.pid
    socket           = /var/run/mysqld/mysqld.sock
    datadir          = /var/lib/mysql
    log-error        = /var/log/mysql/error.log
    bind-address     = 0.0.0.0
    secure-file-priv = NULL
    max_connections  = 16384
    
    character-set-server = utf8mb4
    collation-server     = utf8mb4_general_ci
    init_connect         ='SET NAMES utf8mb4'
    skip-name-resolve
    skip-host-cache
    
    server_id = 
    log-bin = mysql-bin
    log-slave-updates
    sync_binlog = 0
    innodb_flush_log_at_trx_commit = 0
    replicate-do-db = test
    slave-net-timeout = 60
    log_bin_trust_function_creators = 1
    
    # Custom config should go here
    !includedir /etc/mysql/conf.d/
    复制代码

配置结束即可启动。

主从设置

  1. 获取 Master 状态。

    登录 Master,输入 show master status\GFile 指当前的 bin-log 文件,Position 指当前的偏移 offset,下一条命令从这里开始。因此我们需要记住这两个信息,从库依靠这两个信息开始进行复制的。

    mysql> show master status\G
    *************************** 1. row ***************************
                 File: mysql-bin.000003
             Position: 156
         Binlog_Do_DB: test
     Binlog_Ignore_DB:
    Executed_Gtid_Set:
    1 row in set (0.00 sec)
    复制代码
  2. 登录从库,清空原 slave 信息。

    mysql> reset slave;
    Query OK, 0 rows affected, 1 warning (0.00 sec)
    复制代码
  3. 从库配置 slave 信息。

    注意,master_port=3306,因为容器内部网络访问的是容器的接口,而非宿主机的接口。读取 Master 的 FilePosition 信息在这里配置。

    我在 my.cnf 中开启了远程连接,所以这里是可以直接连的。如果不是按上方 bind-address 配置来的话,未开启请开启,否则从库没法登录主库的。想要看从库机器能否连接主库,可以在从库中用 mysql 客户端连接主库试试。

    mysql> change master to master_host='172.25.0.101',master_user='root',master_password='123456',master_port=3306,master_log_file='mysql-bin.000003',master_log_pos=156;
    Query OK, 0 rows affected, 8 warnings (0.05 sec)
    复制代码
  4. 启动 slave 连接。

    mysql> start slave;
    Query OK, 0 rows affected, 1 warning (0.01 sec)
    复制代码
  5. 查看 slave 状态,如果 Slave_IO_RunnigSlave_SQL_Running 进程都为 Yes,表示都启动成功,则可以进行复制测试了。

    Slave_IO 进程负责主机通信,用于连接主库,并拉取主库 bin-log 到 relay-log。
    Slave_SQL 进程负责执行命令,用于执行 relay-log 中的操作。

    如果发现其中一个不为 Yes,可以去 error.log 中查看具体信息。

    mysql> show slave status\G
    *************************** 1. row ***************************
                   Slave_IO_State: Waiting for source to send event
                      Master_Host: 172.25.0.101
                      Master_User: root
                      Master_Port: 3306
                    Connect_Retry: 60
                  Master_Log_File: mysql-bin.000003
              Read_Master_Log_Pos: 156
                   Relay_Log_File: 34d0770589c6-relay-bin.000002
                    Relay_Log_Pos: 324
            Relay_Master_Log_File: mysql-bin.000003
                 Slave_IO_Running: Yes
                Slave_SQL_Running: Yes
                  Replicate_Do_DB: test
              Replicate_Ignore_DB:
                                ...
    1 row in set, 1 warning (0.01 sec)
    复制代码
  • 复制测试

    在主机的 test 库中新建一个表,从库就会新建一个表。在表中增、删、改数据,从库就会增、删、改数据。

禁止从库更新

在从库上执行 set global read_only=1; 命令,可以限制普通用户的写操作,包括增、删、改、新建表、修改表、删除表。

mysql> set global read_only=1;
Query OK, 0 rows affected (0.00 sec)
复制代码

设置失去效果?

但是如果你照着上文的操作来,会发现这里依旧可以执行成功(WTF?):

mysql> drop table t1;
Query OK, 0 rows affected (0.07 sec)
复制代码

这是因为,这个操作仅仅限制了普通用户,无法限制超级用户,root 是可以为所欲为的。如果要限制 root,就得这么玩:

mysql> flush tables with read lock;
Query OK, 0 rows affected (0.02 sec)

mysql> set global read_only=1;
Query OK, 0 rows affected (0.00 sec)

mysql> drop table t1;
ERROR 1223 (HY000): Can't execute the query because you have a conflicting read lock
复制代码

但是这样玩的话,从库就无法复制主库了,因为这样也锁死了 Slave_SQL 进程无法执行写操作。所以,超级权限用户是不能动的。使用 unlock tables; 可以解开 flush tables with read lock; 操作,此时从库就可以复制主库操作了。

正确的复制

正确的做法是,复制时所使用的用户是 Master 上新建的普通用户即可,这样 set global read_only=1; 就会生效。同时保证从库 root 这些超级权限账号只能本地登录,不允许外界登录和进行写操作即可。

那为什么上面不直接按正确的来?
如果你那么喜欢重复,就让你体验一下

要改用户,主库三个连招:

CREATE USER 'slave'@'%' IDENTIFIED WITH sha256_password BY 'slave123';
GRANT REPLICATION SLAVE ON *.* TO 'slave'@'%';
FLUSH privileges;
show master status\G
复制代码

从库:

stop slave;
reset slave;
change master to master_host='172.25.0.101',master_user='slave',master_password='slave123',master_port=3306,master_log_file='mysql-bin.******',master_log_pos=*****;
start master;
复制代码

坑很多,有的坑是自己的逻辑错误,有的坑是对 MySQL 的认识不足,踩到了肯定很烦,但是有调查才有发言权,经历过才有真功夫。

文章分类
后端
文章标签