
📚 本文收录于「bksczm」的系列专栏
关于分布式和集群架构可以看Redis基础篇之初识分布式-CSDN博客,本篇博客以实现主从复制为主。
一、先搞懂原理:主从复制是怎么工作的
1.1 拓扑结构
我们要搭的是一主两从:
写请求(INSERT/UPDATE/DELETE/DDL)
│
▼
┌────────────────┐
│ Master 主库 A │
│ 53306 │
└───────┬────────┘
推送binlog│ 推送binlog
┌────────┴────────┐
▼ ▼
┌────────────────┐ ┌────────────────┐
│ Slave 从库 B │ │ Slave 从库 C │
│ 53307 │ │ 53308 │
└────────────────┘ └────────────────┘
承担读请求 承担读请求
1.2 三个线程的完整工作流程
主从复制依赖 binlog(二进制日志) 和 relay log(中继日志),涉及三个线程:
主库 Master 从库 Slave
┌──────────────┐ ┌──────────────────────┐
│ 客户端提交事务 │ │ │
│ │ │ │ │
│ ▼ │ │ │
│ 写 binlog │ │ ① IO 线程 │
│ │ │ binlog事件 │ 连接主库、请求binlog │
│ ③ Binlog │◄───────────────│ 写入本地 relay log │
│ Dump 线程 │ 网络 │ │ │
│ 主动推送 │ ───────────────►│ ▼ │
└──────────────┘ │ ② SQL 线程 │
│ 读 relay log 重放 │
│ 变更本地数据 │
└──────────────────────┘
- 主库的 Binlog Dump 线程:从库连上来后,主库为每个从库创建一个 dump 线程,负责把 binlog 事件发送给从库;
- 从库的 IO 线程:连接主库、请求 binlog,收到后写入本地的 relay log(中继日志);
- 从库的 SQL 线程:读取 relay log,按顺序重放(redo)成数据变更,使从库数据与主库一致。
所以主从延迟可以发生在两段:IO 线程拉取慢(网络)、SQL 线程重放慢(大事务/单线程)。
1.3 复制的三种同步方式
| 方式 | 原理 | 特点 |
|---|---|---|
| 异步复制(默认) | 主库本地提交完就返回客户端,不等从库 | 性能最好,主库宕机可能丢失已提交事务 |
| 半同步复制 semi-sync | 主库至少等一个从库确认收到 binlog(写入 relay log)才返回 | 折中,数据更安全;等待超时(默认 1 秒)后自动降级为异步 |
| 全同步 | 等所有从库都执行完 | 性能太差,MySQL 原生不直接提供,可用 MGR 等方案实现 |
1.4 关键前提:新从库要不要先导数据
- 本示例是全新空主库:直接从
SHOW MASTER STATUS看到的位点开始同步即可; - 如果主库已经有大量历史数据:MySQL 复制不会自动把历史数据全量搬给从库(这点和 Redis 主从会自动做 RDB 全量同步不同),必须先手动初始化:
- 用
mysqldump --single-transaction --source-data=2或 XtraBackup 给主库做全量备份(--source-data会在文件头记录备份位点); - 把备份导入空从库;
- 再从备份位点开始
CHANGE REPLICATION SOURCE TO ...+START REPLICA。
- 用
二、Docker 基础:用容器模拟多台主机
2.1 准确理解容器、镜像、仓库
- 镜像(Image):一个只读模板,把可执行文件和依赖(库、配置、运行环境)打包在一起;
- 容器(Container):镜像运行起来的实例(镜像相当于"类",容器相当于"对象");
- 仓库(Registry):存放镜像的地方,如 Docker Hub、阿里云镜像仓库;
- 容器与宿主机共享同一个操作系统内核(不是各自装一个完整 OS),容器之间通过 Namespace 隔离(进程、网络、文件系统等),通过 Cgroup 限制(CPU、内存),所以容器比虚拟机轻量得多。
2.2 安装 Docker(Ubuntu)
# 1. 更新索引
sudo apt update
# 2. 安装依赖和工具
sudo apt install -y ca-certificates curl gnupg lsb-release
# 3. 创建密钥目录
sudo mkdir -p /etc/apt/keyrings
# 4. 添加阿里云 Docker GPG 密钥
curl -fsSL [https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg](https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg) | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
# 5. 添加阿里云 Docker 软件源
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
[https://mirrors.aliyun.com/docker-ce/linux/ubuntu](https://mirrors.aliyun.com/docker-ce/linux/ubuntu) $(lsb_release -cs) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 6. 安装 Docker
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
# 7. 验证
sudo systemctl status docker
sudo docker version
防火墙:教学环境为省事可
sudo systemctl stop ufw;生产环境不要直接关防火墙,而是按需放行端口。容器之间互联默认走 Docker 自己的网桥,与宿主机防火墙规则是两套东西。
2.3 配置镜像加速
编辑 /etc/docker/daemon.json(国内拉 Docker Hub 常超时,配置加速器;部分免费加速器近年陆续失效,失效时更换可用地址即可):
{
"registry-mirrors": [
"[https://docker.m.daocloud.io](https://docker.m.daocloud.io)",
"[https://dockerhub.timeweb.cloud](https://dockerhub.timeweb.cloud)",
"[https://huecker.io](https://huecker.io)"
]
}
加载并重启:
sudo systemctl daemon-reload
sudo systemctl restart docker
docker info # 查看 Registry Mirrors 是否生效
2.4 创建专用网络(容器互联用)
让三个容器通过容器名互相访问:
docker network create mysql-net
不用自建网络也可以,但那样容器之间只能用宿主机 IP + 映射端口互联,比较麻烦。
三、搭建主库(Master)
3.1 目录规划(挂载卷,数据持久化)
mkdir -p /czm/mysql/master/conf
mkdir -p /czm/mysql/master/mysql
3.2 启动主库容器
docker run -d \
--name czm-mysql-master \
--network mysql-net \
-p 53306:3306 \
-v /czm/mysql/master/conf:/etc/mysql/conf.d \
-v /czm/mysql/master/mysql:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--restart=always \
mysql:8.0
逐项解释原理:
| 参数 | 作用 |
|---|---|
docker run | 由镜像创建并启动容器;本地没有该镜像时自动从 Registry 拉取 |
-d | 后台运行 |
--name | 容器名,后续 docker exec/restart/stop 都用它 |
--network mysql-net | 加入自建网络,之后从库可直接用容器名 czm-mysql-master 访问 |
-p 53306:3306 | 端口映射:宿主机 53306 → 容器内 3306(docker-proxy/iptables 做 NAT),方便从宿主机外部用客户端连接 |
-v 主机目录:容器目录 | 数据卷绑定挂载:容器内写 /etc/mysql/conf.d、/var/lib/mysql 实际落到宿主机,容器删除后数据和配置仍保留 |
-e MYSQL_ROOT_PASSWORD=123456 | 注入环境变量;MySQL 官方镜像的入口脚本首次启动时读取它初始化 root 密码 |
--restart=always | Docker 重启后容器自动拉起 |
mysql:8.0 | 镜像名:标签 |
官方镜像首次启动时发现
/var/lib/mysql为空,会自动执行初始化(建系统库、设 root 密码);之后再启动发现目录非空则直接启动。
3.3 编写主库配置
在 /czm/mysql/master/conf/my.cnf 中写入:
[mysqld]
# 集群内必须唯一
server-id=13803306
# 开启 binlog 及基本名
log-bin=binlog
# 行格式,生产推荐
binlog_format=ROW
# binlog 保留 10 天(默认 30 天 = 2592000 秒)
binlog_expire_logs_seconds=864000
# 双1配置之一:每次事务提交都 fsync binlog
sync-binlog=1
# 只复制/忽略指定库(默认复制全部)
# binlog-do-db=db_name
# binlog-ignore-db=db_name
3.4 重启并查看复制位点
docker restart czm-mysql-master
进入主库:
docker exec -it czm-mysql-master mysql -uroot -p123456
查看位点:
SHOW MASTER STATUS;
+---------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+---------------+----------+--------------+------------------+-------------------+
| binlog.000001 | 157 | | | |
+---------------+----------+--------------+------------------+-------------------+
记住 File 和 Position(你的实际值可能不同),从库要从这个位置开始同步。
3.5 创建复制账号(必须做)
从库需要一个账号连主库拉 binlog,在主库执行:
CREATE USER 'repl'@'%' IDENTIFIED BY '123456';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;
'%' 表示允许任意主机连接(Docker 内网环境)。
如果外部连接不上:
1.可能是操作不对
2.如果你的是云服务器,检查一下云服务器的防火墙对对应端口号是否开启!!!
四、搭建从库(Slave B)
4.1 目录与容器
mkdir -p /czm/mysql/slave1/conf
mkdir -p /czm/mysql/slave1/mysql
docker run -d \
--name czm-mysql-slave1 \
--network mysql-net \
-p 53307:3306 \
-v /czm/mysql/slave1/conf:/etc/mysql/conf.d \
-v /czm/mysql/slave1/mysql:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
--restart=always \
mysql:8.0
4.2 从库配置
在 /czm/mysql/slave1/conf/my.cnf 中写入:
[mysqld]
# 必须与主库不同,集群内唯一
server-id=13803307
log-bin=binlog
binlog_format=ROW
binlog_expire_logs_seconds=864000
sync-binlog=1
################## 从库专属配置 ##################
# 中继日志基本名
relay-log=relay-bin
# 启动时不自动开启复制,稍后手动 START REPLICA
# (8.0.24 前用 skip-slave-start=ON)
skip-replica-start=ON
# 从库把自己重放的变更也写入自己的 binlog
# 链式复制 A->B->C 时 B 必须开启(8.0.26 前为 log_slave_updates)
# log-replica-updates=ON
docker restart czm-mysql-slave1
4.3 在从库建立复制关系
进入从库:
docker exec -it czm-mysql-slave1 mysql -uroot -p123456
CHANGE REPLICATION SOURCE TO
SOURCE_HOST='czm-mysql-master', -- 自建网络下直接用容器名
SOURCE_PORT=3306, -- 容器内端口,不是宿主机映射的53306
SOURCE_USER='repl',
SOURCE_PASSWORD='123456',
SOURCE_LOG_FILE='binlog.000001', -- 来自主库 SHOW MASTER STATUS
SOURCE_LOG_POS=157;
关于
SOURCE_HOST:因为我们建了mysql-net自定义网络,直接填容器名;如果没建网络,就填宿主机 IP(或172.17.0.1Docker 默认网桥地址),端口填映射端口 53306。
4.4 启动复制
START REPLICA;
4.5 查看复制状态
SHOW REPLICA STATUS\G
重点看两个字段必须都是 Yes:
- Replica_IO_Running: Yes:IO 线程正常(网络连通、能拉到 binlog);
- Replica_SQL_Running: Yes:SQL 线程正常(relay log 重放无报错)。
其他常用字段:
Last_IO_Error/Last_SQL_Error:出错时的具体原因(连不上、密码错、位点不对、主键冲突等);Seconds_Behind_Master:从库落后主库的秒数(NULL 表示复制未运行)。
在主库/从库执行 SHOW PROCESSLIST; 可以看到:
- 主库上有一个
Binlog Dump线程(对应一个从库连接); - 从库上有
replica_IO和replica_SQL两个线程。
4.6 第二个从库 C
完全重复从库 B 的步骤,注意三处区别:
- 宿主机端口
-p 53308:3306,容器名czm-mysql-slave2; - 目录换成
/czm/mysql/slave2/...; - server-id 必须再换一个(如
13803308),复制位点与 B 相同。
五、验证主从同步
在主库写数据:
CREATE DATABASE demo;
USE demo;
CREATE TABLE t(id INT PRIMARY KEY, name VARCHAR(20));
INSERT INTO t VALUES(1,'zhangsan'),(2,'lisi');
在两个从库查询:
SELECT * FROM demo.t;
能立刻看到数据,说明复制链路正常。再尝试在从库直接写(默认是允许的,除非加 read_only=1),生产环境应设置从库只读:
# 从库 my.cnf 追加
read_only=ON
super_read_only=ON
六、并行复制与主从延迟
6.1 并行回放
从库默认早期用单线程重放 relay log,主库高并发写入时容易跟不上。8.0 支持多线程并行复制:
SHOW VARIABLES LIKE 'replica_parallel_workers'; -- 8.0.27+ 默认 4
- 设为
0:单线程串行回放(最朴素,性能最低); - 设为 N(如 4/8):开启 N 个 worker 并行回放;
- 配合
replica_parallel_type=LOGICAL_CLOCK(基于主库组提交并行)或WRITESET(基于写集合,并行度更高)。
6.2 主从延迟的常见原因与对策
- 原因:大事务、大批量 DML、DDL、从库硬件差、网络抖动、单线程回放;
- 对策:
- 开启并行复制;
- 避免大事务、批量操作拆分;
- 对"写完立刻读"的强一致场景(如支付后跳订单页),读请求直接走主库,或用半同步 + 读一致性策略;
- 监控
Seconds_Behind_Master。
七、读写分离与高可用
主从复制本身只是数据同步,读写分离和高可用还需要额外组件:
- 读写分离:写请求(INSERT/UPDATE/DELETE/DDL)全部路由到主库,读请求(SELECT)分发到多个从库;
- 静态分离:应用代码里硬编码数据源;
- 动态分离:通过中间件(ProxySQL、MaxScale、MyCat、ShardingSphere-JDBC)或应用层动态数据源自动路由;
- 负载均衡:读请求在多个从库间分发,摊平读压力;
- 故障转移(Failover):主库宕机时,需要把某个从库提升为新主(重新选主、其他从库改连新主),人工操作太慢,通常用 MHA、Orchestrator、MySQL MGR(组复制) 或云厂商的高可用方案自动完成;
- 数据一致性权衡:异步复制有延迟,从库可能读到旧数据,业务必须评估哪些读可以容忍延迟。
八、进阶:GTID 复制
传统复制要手动指定 LOG_FILE + LOG_POS,主从切换、找位点很麻烦。MySQL 5.6+ 支持 GTID(全局事务标识),每个事务有全局唯一 ID:
# 主从库都加
gtid_mode=ON
enforce_gtid_consistency=ON
建立复制时无需手填位点,改为自动定位:
CHANGE REPLICATION SOURCE TO
SOURCE_HOST='czm-mysql-master',
SOURCE_PORT=3306,
SOURCE_USER='repl',
SOURCE_PASSWORD='123456',
SOURCE_AUTO_POSITION=1;
START REPLICA;
GTID 让从库自动判断从哪里续传,主从切换也更简单,是生产环境推荐方式。
转载自 CSDN-专业IT技术社区




