一、前置准备:Docker 安装
1. 卸载旧版本 Docker(可选)
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
代码解释:
sudo:以管理员权限执行命令yum remove:通过 yum 包管理器卸载指定软件- 后续列出的是 Docker 旧版本的相关组件,确保彻底清理旧版本,避免冲突
2. 设置 Docker 仓库(这里国内一般用镜像源,之前拉去镜像的文章中有阿里云网址)
sudo yum install -y yum-utils
sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
代码解释:
yum install -y yum-utils:安装 yum 工具包(包含 yum-config-manager),-y表示自动确认安装yum-config-manager --add-repo:添加 Docker 官方 yum 仓库,指定仓库地址为 Docker CE 的 CentOS 版本仓库
3. 安装 Docker 引擎
sudo yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
代码解释:
docker-ce:Docker 社区版引擎(核心组件)docker-ce-cli:Docker 命令行客户端containerd.io:容器运行时(管理容器生命周期)docker-buildx-plugin:扩展 Docker 构建功能的插件docker-compose-plugin:支持 docker-compose 命令的插件
4. 启动并设置 Docker 开机自启
sudo systemctl start docker
sudo systemctl enable docker
代码解释:
systemctl start docker:启动 Docker 服务systemctl enable docker:设置 Docker 服务开机自动启动
5. 验证 Docker 安装
一开始找不到就会自动拉取
sudo docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
代码解释:
- 拉取并运行官方的
hello-world镜像,若输出欢迎信息则说明 Docker 安装成功
二、MySQL 双主双从架构部署
mysql-master1 (3306) <-------> mysql-master2 (3308) 【双主互相同步】
↑ ↑
│ │
↓ ↓
mysql-slave1 (3307) mysql-slave2 (3309) 【从库只读】
| 角色 | 容器名 | 主机端口 | 容器端口 | server-id | 角色说明 |
|---|---|---|---|---|---|
| 主库 1 | mysql-master1 | 3306 | 3306 | 11 | 可写,双主之一 |
| 主库 2 | mysql-master2 | 3308 | 3306 | 33 | 可写,双主之一 |
| 从库 1 | mysql-slave1 | 3307 | 3306 | 22 | 只读,同步 master1 |
| 从库 2 | mysql-slave2 | 3309 | 3306 | 44 | 只读,同步 master2 |
1. 创建 Docker 自定义网络并拉去mysql镜像
[root@localhost ~]# docker network create mysql_net
[root@localhost ~]#docker pull mysql:8.4.8
代码解释:
docker network create:创建 Docker 自定义网络mysql_net:网络名称,用于让所有 MySQL 容器在同一网络下互通(容器间可通过容器名访问)
2. 配置 MySQL 主从节点配置文件(先把配置文件写好,而不是进入镜像写)
(1)master1 配置文件
# mysql-master1
[mysqld]
server_id=11
binlog-ignore-db=mysql
binlog-ignore-db=information_schema
log-bin=mysql-bin
auto-increment-increment=2
auto-increment-offset=1
代码解释:
[mysqld]:MySQL 服务端配置段标识server_id=11:数据库实例唯一标识(主从架构中所有节点 id 必须不同)binlog-ignore-db=mysql:二进制日志忽略mysql系统库(不记录该库的操作)binlog-ignore-db=information_schema:忽略information_schema系统库log-bin=mysql-bin:开启二进制日志(主从同步的核心,记录数据变更),日志文件前缀为mysql-binauto-increment-increment=2:自增字段步长为 2(避免双主写入时主键冲突)auto-increment-offset=1:自增字段起始偏移为 1(master1 的自增 id 为 1、3、5...)
(2)master2 配置文件
# mysql-master2
[mysqld]
server_id=33
binlog-ignore-db=mysql
binlog-ignore-db=information_schema
log-bin=mysql-bin
auto-increment-increment=2
auto-increment-offset=2
代码解释:
server_id=33:master2 的唯一标识,与 master1 区分auto-increment-offset=2:自增字段起始偏移为 2(master2 的自增 id 为 2、4、6...)- 其余参数与 master1 一致,核心是保证双主自增 id 不冲突
(3)slave1 配置文件
# mysql-slave1
[mysqld]
server_id=22
log-bin=mysql-slave-bin
relay_log=mysql-relay
read_only=1
代码解释:
server_id=22:slave1 唯一标识log-bin=mysql-slave-bin:开启 slave 的二进制日志(可选,若 slave 需作为其他节点的主库则必须)relay_log=mysql-relay:开启中继日志(主从同步时,slave 先将 master 的 binlog 写入中继日志,再重放)read_only=1:设置 slave 为只读模式(仅对普通用户生效,root 仍可写)
(4)slave2 配置文件
# mysql-slave2
[mysqld]
server_id=44
log-bin=mysql-slave-bin
relay_log=mysql-relay
read_only=1
代码解释:
server_id=44:slave2 唯一标识- 其余参数与 slave1 一致
3. 创建目录并挂载配置文件(关键前置步骤)
# 创建master1相关目录
mkdir -p /data/master1/log /data/master1/data /data/master1/conf
# 将上述master1配置写入/my.cnf
echo -e "[mysqld]\nserver_id=11\nbinlog-ignore-db=mysql\nbinlog-ignore-db=information_schema\nlog-bin=mysql-bin\nauto-increment-increment=2\nauto-increment-offset=1" > /data/master1/conf/my.cnf
# 创建master2相关目录
mkdir -p /data/master2/log /data/master2/data /data/master2/conf
echo -e "[mysqld]\nserver_id=33\nbinlog-ignore-db=mysql\nbinlog-ignore-db=information_schema\nlog-bin=mysql-bin\nauto-increment-increment=2\nauto-increment-offset=2" > /data/master2/conf/my.cnf
# 创建slave1相关目录
mkdir -p /data/slave1/log /data/slave1/data /data/slave1/conf
echo -e "[mysqld]\nserver_id=22\nlog-bin=mysql-slave-bin\nrelay_log=mysql-relay\nread_only=1" > /data/slave1/conf/my.cnf
# 创建slave2相关目录
mkdir -p /data/slave2/log /data/slave2/data /data/slave2/conf
echo -e "[mysqld]\nserver_id=44\nlog-bin=mysql-slave-bin\nrelay_log=mysql-relay\nread_only=1" > /data/slave2/conf/my.cnf
代码解释:
mkdir -p:递归创建目录(不存在的父目录自动创建)/data/master1/log:挂载容器内 MySQL 日志目录/data/master1/data:挂载容器内 MySQL 数据目录(持久化数据)/data/master1/conf:挂载容器内 MySQL 配置目录echo -e:将配置内容写入my.cnf文件,-e支持换行符\n
4. 创建 master1 容器
docker run --name mysql-master1 \
--restart=always \
-p 3306:3306 \
-v /data/master1/log:/var/log/mysql \
-v /data/master1/data:/var/lib/mysql \
-v /data/master1/conf/my.cnf:/etc/my.cnf \
-e MYSQL_ROOT_PASSWORD=123456 \
--network mysql_net \
-d mysql:8.4.8
可能会遇到端口被占用或者你失败后重新创建告诉已存在的问题
端口用lsof或者ss -tulnp去查看
已存在就用docker rm mysql-master1
查看所有容器用docker ps -a
代码解释:
docker run:创建并运行容器--name mysql-master1:指定容器名称为mysql-master1--restart=always:容器退出时自动重启(保证服务高可用)-p 3306:3306:端口映射(宿主机 3306 端口映射到容器内 3306 端口)-v /data/master1/log:/var/log/mysql:目录挂载(宿主机目录:容器内目录),持久化日志-v /data/master1/data:/var/lib/mysql:持久化 MySQL 数据(核心,容器删除后数据不丢失)-v /data/master1/conf/my.cnf:/etc/my.cnf:挂载自定义配置文件覆盖容器默认配置-e MYSQL_ROOT_PASSWORD=123456:设置 MySQL root 用户密码(环境变量)--network mysql_net:将容器加入mysql_net自定义网络-d:后台运行容器mysql:8.4.8:指定使用的 MySQL 镜像版本
5. 创建 master2 容器
docker run --name mysql-master2 \
--restart=always \
-p 3308:3306 \
-v /data/master2/log:/var/log/mysql \
-v /data/master2/data:/var/lib/mysql \
-v /data/master2/conf/my.cnf:/etc/my.cnf \
-e MYSQL_ROOT_PASSWORD=123456 \
--network mysql_net \
-d mysql:8.4.8
代码解释:
--name mysql-master2:容器名称mysql-master2-p 3308:3306:宿主机 3308 端口映射容器内 3306(避免与 master1 端口冲突)- 其余参数与 master1 一致,仅挂载目录和容器名不同
6. 创建 slave1 容器
docker run --name mysql-slave1 \
--restart=always \
-p 3307:3306 \
-v /data/slave1/log:/var/log/mysql \
-v /data/slave1/data:/var/lib/mysql \
-v /data/slave1/conf/my.cnf:/etc/my.cnf \
-e MYSQL_ROOT_PASSWORD=123456 \
--network mysql_net \
-d mysql:8.4.8
代码解释:
--name mysql-slave1:容器名称mysql-slave1-p 3307:3306:宿主机 3307 端口映射容器内 3306- 其余参数逻辑与 master 一致
7. 创建 slave2 容器
docker run --name mysql-slave2 \
--restart=always \
-p 3309:3306 \
-v /data/slave2/log:/var/log/mysql \
-v /data/slave2/data:/var/lib/mysql \
-v /data/slave2/conf/my.cnf:/etc/my.cnf \
-e MYSQL_ROOT_PASSWORD=123456 \
--network mysql_net \
-d mysql:8.4.8
代码解释:
--name mysql-slave2:容器名称mysql-slave2-p 3309:3306:宿主机 3309 端口映射容器内 3306- 其余参数逻辑与 master 一致
8. 配置 master1 同步用户(进入 master1 容器执行)
docker exec -it mysql-master1 /bin/bash
这里可能会遇见刚进去就被卡出来的情况
极大的原因是因为虚拟机内容不够
[root@test1 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STA NAMES
e7a75084cfd2 mysql:8.4.8 "docker-entrypoint.s..." 4 minutes ago Up ->3306/tcp mysql-slave2
f38f3d38387d mysql:8.4.8 "docker-entrypoint.s..." 4 minutes ago Res mysql-slave1
a720712b96a3 mysql:8.4.8 "docker-entrypoint.s..." 5 minutes ago Res mysql-master2
0901a808a614 mysql:8.4.8 "docker-entrypoint.s..." 5 minutes ago Up 33060/tcp mysql-master1
up说明容器正常跑起来了,而另外两个陷入无限重启,因为oom
关闭虚拟机加大内存
代码解释:
docker exec -it:进入运行中的容器(-it为交互式终端)mysql-master1:目标容器名/bin/bash:执行 bash 终端
进入容器后执行 MySQL 命令:
刚进入容器相当于一个小型linux,要先进入mysql
bash-5.1# mysql -uroot -p123456
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.4.8 MySQL Community Server - GPL
Copyright (c) 2000, 2026, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
mysql> create user 'repl_user'@'%' identified by '123456';
代码解释:
-
create user 'repl_user'@'%':创建用户repl_user,%表示允许从任意主机连接 -
identified by '123456':设置密码为 123456mysql> create user 'slave_sync_user'@'%' identified by '123456';
代码解释:
-
创建专用于 slave 同步的用户
slave_sync_usermysql> grant replication slave on . to 'repl_user'@'%';
代码解释:
-
grant replication slave on *.*:授予repl_user复制权限(主从同步必需),*.*表示所有库所有表 -
to 'repl_user'@'%':将权限赋予该用户mysql> grant replication slave on . to 'slave_sync_user'@'%';
代码解释:
-
给
slave_sync_user授予同样的复制权限mysql> flush privileges;
代码解释:
-
刷新权限表(使权限配置立即生效)
mysql> show binary log status;
mysql> show binary log status;
+------------------+----------+--------------+--------------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+--------------------------+-------------------+
| mysql-bin.000031 | 1448 | | mysql,information_schema | |
+------------------+----------+--------------+--------------------------+-------------------+
1 row in set (0.00 sec)
代码解释:
- 查看二进制日志状态,输出包含:
File:当前二进制日志文件名(如mysql-bin.000031)Position:当前日志偏移量(如 1448)- 这两个值是配置从库同步的关键参数
9. 配置 slave1 同步 master1
进入 slave1 容器:
docker exec -it mysql-slave1 /bin/bash
mysql -uroot -p123456
这里可能会遇到slave1登入不进去mysql的情况,可能是原本文件残留的问题,建议删除原本挂载文件中产生的数据后重新创建一个容器
rm -rf /data/slave1/data/*
也有可能目录残留旧数据,还有-e没有生效,这里直接mysql就可以进入,root根本没有对应的密码
代码解释:
mysql -uroot -p123456:使用 root 用户登录 MySQL,密码 123456
执行同步配置:
mysql> change replication source to \
-> source_host='mysql-master1',
-> source_user='slave_sync_user',
-> source_password='123456',
-> source_log_file='mysql-bin.000031',
-> source_log_pos=1448,
-> get_source_public_key=1;
代码解释:
-
change replication source to:MySQL 8.0 + 版本配置主从同步的命令(替代旧版change master to) -
source_host='mysql-master1':主库地址(容器名,因在同一网络可直接解析) -
source_user='slave_sync_user':同步使用的用户 -
source_password='123456':同步用户密码 -
source_log_file='mysql-bin.000031':主库当前二进制日志文件名(需与 master1 的show binary log status结果一致) -
source_log_pos=1448:主库当前日志偏移量(需与 master1 的结果一致) -
get_source_public_key=1:MySQL 8.0 默认使用 caching_sha2_password 认证,需获取主库公钥mysql> start replica;
代码解释:
-
启动从库同步进程(替代旧版
start slave)mysql> show replica status\G
代码解释:
- 查看从库同步状态(
\G按行格式化输出) - 关键检查项:
Replica_IO_Running: Yes:IO 线程运行正常(负责读取主库 binlog)Replica_SQL_Running: Yes:SQL 线程运行正常(负责重放中继日志)Replica_IO_State: Waiting for source to send event:IO 线程等待主库发送事件(同步正常状态)
10. 配置 master2 同步用户(与 master1 逻辑一致)
进入 master2 容器:
docker exec -it mysql-master2 /bin/bash
mysql -uroot -p123456
执行用户创建与授权:
mysql> create user 'repl_user'@'%' identified by '123456';
mysql> create user 'slave_sync_user'@'%' identified by '123456';
mysql> grant replication slave on *.* to 'repl_user'@'%';
mysql> grant replication slave on *.* to 'slave_sync_user'@'%';
mysql> flush privileges;
mysql> show binary log status;
mysql> show binary log status;
+------------------+----------+--------------+--------------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+--------------------------+-------------------+
| mysql-bin.000027 | 1447 | | mysql,information_schema | |
+------------------+----------+--------------+--------------------------+-------------------+
1 row in set (0.00 sec)
代码解释:
- 与 master1 的用户配置逻辑完全一致,最后查看 master2 的 binlog 状态(获取 File 和 Position)
11. 配置 slave2 同步 master2
进入 slave2 容器:
docker exec -it mysql-slave2 /bin/bash
mysql -uroot -p123456
执行同步配置:
mysql> change replication source to \
-> source_host='mysql-master2',
-> source_user='slave_sync_user',
-> source_password='123456',
-> source_log_file='mysql-bin.000027',
-> source_log_pos=1447,
-> get_source_public_key=1;
mysql> start replica;
mysql> show replica status\G
代码解释:
- 仅
source_host改为mysql-master2,source_log_file和source_log_pos使用 master2 的show binary log status结果,其余逻辑与 slave1 一致
12. 配置双主同步(master2 同步 master1)
在 master2 容器内执行:
mysql> change replication source to \
-> source_host='mysql-master1',
-> source_user='repl_user',
-> source_password='123456',
-> source_log_file='mysql-bin.000031',
-> source_log_pos=1448,
-> get_source_public_key=1;
mysql> start replica;
mysql> show replica status\G
代码解释:
- 双主架构中,master2 作为 master1 的从库,使用
repl_user同步 - 参数逻辑与 slave 同步一致,仅用户为
repl_user
13. 配置双主同步(master1 同步 master2)
先在 master2 容器内查看 binlog 状态:
mysql> show binary log status;
再进入 master1 容器执行:
mysql> change replication source to \
-> source_host='mysql-master2',
-> source_user='repl_user',
-> source_password='123456',
-> source_log_file='mysql-bin.000027',
-> source_log_pos=1447,
-> get_source_public_key=1;
mysql> start replica;
mysql> show replica status\G
代码解释:
- master1 作为 master2 的从库,完成双主互相同步配置
source_log_file和source_log_pos使用 master2 的 binlog 状态结果
14. 数据同步测试
(1)master1 创建数据库测试
在 master1 容器内执行:
mysql> show databases;
mysql> create database mydb;
mysql> show databases;
代码解释:
show databases:查看当前数据库列表create database mydb:创建测试库mydb
分别在 master2、slave1、slave2 容器内执行:
mysql> show databases;
预期结果 :均能看到mydb库,说明 master1 的变更同步到所有节点
(2)master2 创建表并插入数据测试
在 master2 容器内执行:
mysql> use mydb;
mysql> create table t1(id int);
mysql> insert into t1 values(1),(2),(3);
mysql> select * from t1;
代码解释:
use mydb:切换到mydb库create table t1(id int):创建测试表t1insert into t1 values(1),(2),(3):插入测试数据select * from t1:查看插入结果
分别在 master1、slave1、slave2 容器内执行:
mysql> select * from mydb.t1;
预期结果:均能查询到插入的 3 条数据,说明 master2 的变更同步到所有节点
三、核心原理总结
- Docker 网络 :自定义网络
mysql_net保证容器间通过名称互通,无需手动配置 IP - 二进制日志(binlog):主库开启 binlog 记录数据变更,从库通过 IO 线程读取主库 binlog 写入中继日志,SQL 线程重放中继日志实现同步
- 双主自增策略 :
auto-increment-increment=2+auto-increment-offset不同值,避免双主写入主键冲突 - 权限控制 :同步用户仅授予
replication slave权限,最小权限原则保证安全 - 只读从库 :
read_only=1防止从库被误写入,保证数据一致性
四、常见问题排查
- Replica_IO_Running: No :
- 检查主库地址 / 端口 / 用户密码是否正确
- 检查主库 binlog 文件名和偏移量是否正确
- 检查容器网络是否互通(
ping mysql-master1在 slave 容器内测试)
- Replica_SQL_Running: No :
- 主从数据不一致(可重新初始化主库数据后同步)
- 从库执行了主库未执行的操作(导致日志重放失败)
- MySQL 8.0 认证问题 :
- 必须添加
get_source_public_key=1参数,或修改用户认证方式为mysql_native_password
- 必须添加