本文收录于「bksczm」的系列专栏LinuxC/CMySQL面试题Redis关于分布式和集群架构可以看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-data2或 XtraBackup 给主库做全量备份--source-data会在文件头记录备份位点把备份导入空从库再从备份位点开始CHANGE REPLICATION SOURCE TO ...START REPLICA。二、Docker 基础用容器模拟多台主机2.1 准确理解容器、镜像、仓库镜像Image一个只读模板把可执行文件和依赖库、配置、运行环境打包在一起容器Container镜像运行起来的实例镜像相当于类容器相当于对象仓库Registry存放镜像的地方如 Docker Hub、阿里云镜像仓库容器与宿主机共享同一个操作系统内核不是各自装一个完整 OS容器之间通过Namespace 隔离进程、网络、文件系统等通过Cgroup 限制CPU、内存所以容器比虚拟机轻量得多。2.2 安装 DockerUbuntu# 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 映射端口互联比较麻烦。三、搭建主库Master3.1 目录规划挂载卷数据持久化mkdir -p /czm/mysql/master/conf mkdir -p /czm/mysql/master/mysql3.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_PASSWORD123456 \ --restartalways \ mysql:8.0逐项解释原理参数作用docker run由镜像创建并启动容器本地没有该镜像时自动从 Registry 拉取-d后台运行--name容器名后续docker exec/restart/stop都用它--network mysql-net加入自建网络之后从库可直接用容器名czm-mysql-master访问-p 53306:3306端口映射宿主机 53306 → 容器内 3306docker-proxy/iptables 做 NAT方便从宿主机外部用客户端连接-v 主机目录:容器目录数据卷绑定挂载容器内写/etc/mysql/conf.d、/var/lib/mysql实际落到宿主机容器删除后数据和配置仍保留-e MYSQL_ROOT_PASSWORD123456注入环境变量MySQL 官方镜像的入口脚本首次启动时读取它初始化 root 密码--restartalwaysDocker 重启后容器自动拉起mysql:8.0镜像名:标签官方镜像首次启动时发现/var/lib/mysql为空会自动执行初始化建系统库、设 root 密码之后再启动发现目录非空则直接启动。3.3 编写主库配置在/czm/mysql/master/conf/my.cnf中写入[mysqld] # 集群内必须唯一 server-id13803306 # 开启 binlog 及基本名 log-binbinlog # 行格式生产推荐 binlog_formatROW # binlog 保留 10 天默认 30 天 2592000 秒 binlog_expire_logs_seconds864000 # 双1配置之一每次事务提交都 fsync binlog sync-binlog1 # 只复制/忽略指定库默认复制全部 # binlog-do-dbdb_name # binlog-ignore-dbdb_name3.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 B4.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_PASSWORD123456 \ --restartalways \ mysql:8.04.2 从库配置在/czm/mysql/slave1/conf/my.cnf中写入[mysqld] # 必须与主库不同集群内唯一 server-id13803307 log-binbinlog binlog_formatROW binlog_expire_logs_seconds864000 sync-binlog1 ################## 从库专属配置 ################## # 中继日志基本名 relay-logrelay-bin # 启动时不自动开启复制稍后手动 START REPLICA # 8.0.24 前用 skip-slave-startON skip-replica-startON # 从库把自己重放的变更也写入自己的 binlog # 链式复制 A-B-C 时 B 必须开启8.0.26 前为 log_slave_updates # log-replica-updatesONdocker restart czm-mysql-slave14.3 在从库建立复制关系进入从库docker exec -it czm-mysql-slave1 mysql -uroot -p123456CHANGE REPLICATION SOURCE TO SOURCE_HOSTczm-mysql-master, -- 自建网络下直接用容器名 SOURCE_PORT3306, -- 容器内端口不是宿主机映射的53306 SOURCE_USERrepl, SOURCE_PASSWORD123456, SOURCE_LOG_FILEbinlog.000001, -- 来自主库 SHOW MASTER STATUS SOURCE_LOG_POS157;关于SOURCE_HOST因为我们建了mysql-net自定义网络直接填容器名如果没建网络就填宿主机 IP或172.17.0.1Docker 默认网桥地址端口填映射端口 53306。4.4 启动复制START REPLICA;4.5 查看复制状态SHOW REPLICA STATUS\G重点看两个字段必须都是YesReplica_IO_Running: YesIO 线程正常网络连通、能拉到 binlogReplica_SQL_Running: YesSQL 线程正常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_only1生产环境应设置从库只读# 从库 my.cnf 追加 read_onlyON super_read_onlyON六、并行复制与主从延迟6.1 并行回放从库默认早期用单线程重放 relay log主库高并发写入时容易跟不上。8.0 支持多线程并行复制SHOW VARIABLES LIKE replica_parallel_workers; -- 8.0.27 默认 4设为0单线程串行回放最朴素性能最低设为 N如 4/8开启 N 个 worker 并行回放配合replica_parallel_typeLOGICAL_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_modeON enforce_gtid_consistencyON建立复制时无需手填位点改为自动定位CHANGE REPLICATION SOURCE TO SOURCE_HOSTczm-mysql-master, SOURCE_PORT3306, SOURCE_USERrepl, SOURCE_PASSWORD123456, SOURCE_AUTO_POSITION1; START REPLICA;GTID 让从库自动判断从哪里续传主从切换也更简单是生产环境推荐方式。