bksczm头像
MySQL进阶篇之主从复制封面图

MySQL进阶篇之主从复制

  bksczm

📚 本文收录于「bksczm」的系列专栏

LinuxLinuxC/C++C/C++
MySQLMySQL面试题面试题
RedisRedis

关于分布式和集群架构可以看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 重放     │
                                │  变更本地数据          │
                                └──────────────────────┘
  1. 主库的 Binlog Dump 线程:从库连上来后,主库为每个从库创建一个 dump 线程,负责把 binlog 事件发送给从库;
  2. 从库的 IO 线程:连接主库、请求 binlog,收到后写入本地的 relay log(中继日志);
  3. 从库的 SQL 线程:读取 relay log,按顺序重放(redo)成数据变更,使从库数据与主库一致。

所以主从延迟可以发生在两段:IO 线程拉取慢(网络)、SQL 线程重放慢(大事务/单线程)。

1.3 复制的三种同步方式

方式原理特点
异步复制(默认)主库本地提交完就返回客户端,不等从库性能最好,主库宕机可能丢失已提交事务
半同步复制 semi-sync主库至少等一个从库确认收到 binlog(写入 relay log)才返回折中,数据更安全;等待超时(默认 1 秒)后自动降级为异步
全同步等所有从库都执行完性能太差,MySQL 原生不直接提供,可用 MGR 等方案实现

1.4 关键前提:新从库要不要先导数据

  • 本示例是全新空主库:直接从 SHOW MASTER STATUS 看到的位点开始同步即可;
  • 如果主库已经有大量历史数据:MySQL 复制不会自动把历史数据全量搬给从库(这点和 Redis 主从会自动做 RDB 全量同步不同),必须先手动初始化:
    1. 用 mysqldump --single-transaction --source-data=2 或 XtraBackup 给主库做全量备份(--source-data 会在文件头记录备份位点);
    2. 把备份导入空从库;
    3. 再从备份位点开始 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=alwaysDocker 重启后容器自动拉起
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.1 Docker 默认网桥地址),端口填映射端口 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、从库硬件差、网络抖动、单线程回放;
  • 对策:
    1. 开启并行复制;
    2. 避免大事务、批量操作拆分;
    3. 对"写完立刻读"的强一致场景(如支付后跳订单页),读请求直接走主库,或用半同步 + 读一致性策略;
    4. 监控 Seconds_Behind_Master。

七、读写分离与高可用

主从复制本身只是数据同步,读写分离和高可用还需要额外组件:

  1. 读写分离:写请求(INSERT/UPDATE/DELETE/DDL)全部路由到主库,读请求(SELECT)分发到多个从库;
    • 静态分离:应用代码里硬编码数据源;
    • 动态分离:通过中间件(ProxySQL、MaxScale、MyCat、ShardingSphere-JDBC)或应用层动态数据源自动路由;
  2. 负载均衡:读请求在多个从库间分发,摊平读压力;
  3. 故障转移(Failover):主库宕机时,需要把某个从库提升为新主(重新选主、其他从库改连新主),人工操作太慢,通常用 MHA、Orchestrator、MySQL MGR(组复制) 或云厂商的高可用方案自动完成;
  4. 数据一致性权衡:异步复制有延迟,从库可能读到旧数据,业务必须评估哪些读可以容忍延迟。

八、进阶: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技术社区

原文链接:https://blog.csdn.net/bksczm/article/details/168436144

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--