# MySQL 8.4 双主(Source-Source)同步部署文档(跨主机统一目录/容器名版) > **AI 响应规范 (MySQL 执行策略)**: > > 1. **强制参数**: 所有 `docker exec` 命令必须包含 `-h127.0.0.1` 参数。 > 2. **双重输出**: 涉及 SQL 操作时,必须同时输出 `Docker 执行命令` 和 `纯 SQL 脚本`。 ## 文档概述 你需要部署跨两台独立主机的 MySQL 8.4 双主双向复制集群,核心要求是:两台主机的容器名、目录路径统一使用 `mysql`(仅通过 IP 区分节点),保证两节点数据一致性,支持初始化、校验、宕机重启恢复及节点重建全流程。核心设计遵循: - **GTID + AUTO_POSITION**:重启后自动定位同步位点,减少人工干预; - **ROW 模式 binlog**:避免非确定性函数导致的数据不一致; - **持久化数据卷**:独立 Volume 保障容器重启数据不丢失; - **自增键隔离**:通过步长/偏移配置避免双写主键冲突。 ## 1. 环境准备 ### 1.1 节点信息(核心区分点) | 节点 | 主机IP | server_id | auto_increment_offset | 核心标识 | | ---- | ------------ | --------- | --------------------- | ---------- | | 主节点A | ${NODE_A_IP} | 1 | 1 | 生成主键1、3、5… | | 主节点B | ${NODE_B_IP} | 2 | 2 | 生成主键2、4、6… | ### 1.2 系统依赖(两台主机统一执行) ```plain # 安装 Docker & Docker Compose、MySQL 客户端 sudo apt update && sudo apt install -y docker.io docker-compose-plugin mysql-client # 验证依赖(确保版本正常) docker --version && docker compose version && mysql --version ``` ### 1.3 目录规划(两台主机完全一致) ```plain # 两台主机均执行以下命令,目录统一为 /data/mysql sudo mkdir -p /data/mysql/{data,backup,logs} sudo chown 999:999 /data/mysql -R # MySQL 容器默认UID/GID为999,避免权限问题 ``` ## 2. 双主节点部署 ### 2.1 Docker Compose 配置(两台主机完全一致) 创建 `/data/mysql/docker-compose.yml`,内容如下(无节点差异): ```plain version: '3.8' networks: g3fo: driver: bridge services: mysql: image: mysql:8.4.7 container_name: mysql # 两台主机容器名统一为mysql restart: always deploy: replicas: 1 placement: constraints: - node.role == manager ports: - 3306:3306 # 端口统一,通过主机IP区分节点 networks: - g3fo volumes: - /data/mysql/data:/var/lib/mysql - /data/mysql/backup:/backup - /data/mysql/my.cnf:/etc/mysql/conf.d/my.cnf - /data/mysql/logs:/var/log/mysql - /etc/localtime:/etc/localtime - /etc/timezone:/etc/timezone environment: - TZ=Asia/Hong_Kong - MYSQL_ROOT_PASSWORD=afe123456 # 两台主机统一密码,生产建议修改 privileged: true # 保障文件权限操作 ``` ### 2.2 MySQL 配置文件(my.cnf) #### 节点A(${NODE_A_IP}):`/data/mysql/my.cnf` ```plain [mysqld] # ========== 节点唯一标识(核心区分点) ========== server_id=1 # 全局唯一,不可与节点B重复 report_host=${NODE_A_IP} # 当前主机IP,便于复制状态识别 datadir=/var/lib/mysql socket=/var/lib/mysql/mysql.sock # ========== 基础配置(两台主机一致) ========== character-set-server=utf8mb4 max_connections=1000 skip_name_resolve=ON # 关闭DNS解析,提升连接效率 # ========== 复制核心配置(两台主机一致) ========== log_bin=mysql-bin # 开启binlog binlog_format=ROW # 强制ROW模式,保证复制一致性 binlog_row_image=FULL # 记录完整行数据 binlog_expire_logs_seconds=604800 # 7天自动清理binlog gtid_mode=ON # 开启GTID模式 enforce_gtid_consistency=ON # 强制GTID事务一致性 log_slave_updates=ON # 从库同步的事务写入自身binlog(双向复制必需) skip_slave_start=OFF # 重启自动启动复制线程 relay_log_recovery=ON # 重启自动恢复中继日志 max_binlog_size=1G # 单个binlog最大1G # ========== 数据可靠性(两台主机一致) ========== innodb_flush_log_at_trx_commit=1 # 事务提交立即刷盘(ACID) sync_binlog=1 # 每次事务同步binlog到磁盘 # ========== 双主自增键隔离(核心区分点) ========== auto_increment_increment=2 # 自增步长2(两台主机一致) auto_increment_offset=1 # 节点A起始值1(生成1、3、5…) # ========== 日志配置(两台主机一致) ========== log_error = /var/log/mysql/mysql_error.log # 错误日志 log_error_verbosity = 2 # 记录错误+警告 slow_query_log = 1 # 开启慢查询日志 slow_query_log_file = /var/log/mysql/mysql_slow.log long_query_time = 2 # 慢查询阈值2秒 # ========== 读写权限(两台主机一致) ========== read_only=OFF super_read_only=OFF ``` #### 节点B(${NODE_B_IP}):`/data/mysql/my.cnf` ```plain [mysqld] # ========== 节点唯一标识(核心区分点) ========== server_id=2 # 与节点A区分 report_host=${NODE_B_IP} # 当前主机IP datadir=/var/lib/mysql socket=/var/lib/mysql/mysql.sock # ========== 基础配置(与节点A一致) ========== character-set-server=utf8mb4 max_connections=1000 skip_name_resolve=ON # ========== 复制核心配置(与节点A一致) ========== log_bin=mysql-bin binlog_format=ROW binlog_row_image=FULL binlog_expire_logs_seconds=604800 gtid_mode=ON enforce_gtid_consistency=ON log_slave_updates=ON skip_slave_start=OFF relay_log_recovery=ON max_binlog_size=1G # ========== 数据可靠性(与节点A一致) ========== innodb_flush_log_at_trx_commit=1 sync_binlog=1 # ========== 双主自增键隔离(核心区分点) ========== auto_increment_increment=2 auto_increment_offset=2 # 节点B起始值2(生成2、4、6…) # ========== 日志配置(与节点A一致) ========== log_error = /var/log/mysql/mysql_error.log log_error_verbosity = 2 slow_query_log = 1 slow_query_log_file = /var/log/mysql/mysql_slow.log long_query_time = 2 # ========== 读写权限(与节点A一致) ========== read_only=OFF super_read_only=OFF ``` ### 2.3 启动容器(两台主机统一执行) ```plain # 进入目录并启动容器 cd /data/mysql && docker compose up -d # 验证启动状态(确保容器状态为Up) docker ps | grep mysql ``` ## 3. 双主复制初始化 ### 3.1 创建复制专用用户(两台主机统一执行) ```plain # 无需区分节点,直接执行(创建repl用户,允许跨主机访问) docker exec -it mysql mysql -uroot -pafe123456 -h127.0.0.1 -e " CREATE USER 'repl'@'%' IDENTIFIED BY 'afe123456'; GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%'; FLUSH PRIVILEGES; " ``` ### 3.2 配置双向复制(核心:仅IP参数不同) #### 步骤1:节点A(${NODE_A_IP})配置为节点B的从库 ```plain docker exec -it mysql mysql -uroot -pafe123456 -h127.0.0.1 -e " STOP REPLICA; RESET REPLICA ALL; CHANGE REPLICATION SOURCE TO SOURCE_HOST = '${NODE_B_IP}', # 节点B的IP(核心区分点) SOURCE_PORT = 3306, SOURCE_USER = 'repl', SOURCE_PASSWORD = 'afe123456', SOURCE_AUTO_POSITION = 1, # GTID自动定位(无需手动找位点) SOURCE_CONNECT_RETRY = 10, SOURCE_RETRY_COUNT = 9999999999, GET_SOURCE_PUBLIC_KEY = 1; # 适配MySQL 8.0+密码认证 START REPLICA; " ``` #### 步骤2:节点B(${NODE_B_IP})配置为节点A的从库 ```plain docker exec -it mysql mysql -uroot -pafe123456 -h127.0.0.1 -e " STOP REPLICA; RESET REPLICA ALL; CHANGE REPLICATION SOURCE TO SOURCE_HOST = '${NODE_A_IP}', # 节点A的IP(核心区分点) SOURCE_PORT = 3306, SOURCE_USER = 'repl', SOURCE_PASSWORD = 'afe123456', SOURCE_AUTO_POSITION = 1, SOURCE_CONNECT_RETRY = 10, SOURCE_RETRY_COUNT = 9999999999, GET_SOURCE_PUBLIC_KEY = 1; START REPLICA; " ``` ## 4. 同步状态校验 ### 4.1 核心校验命令(两台主机统一执行) **A. Docker 执行模式 (包含 -h127.0.0.1):** ```bash # 查看复制状态 docker exec -it mysql mysql -uroot -pafe123456 -h127.0.0.1 -e "SHOW REPLICA STATUS\G" ``` **B. 纯 SQL 模式:** ```sql SHOW REPLICA STATUS\G ``` ### 4.2 关键校验字段(必须全部满足) | 字段 | 目标值 | 说明 | | --------------------- | -------------------- | ---------------- | | Replica_IO_Running | Yes | IO线程正常(接收binlog) | | Replica_SQL_Running | Yes | SQL线程正常(执行事务) | | Last_SQL_Error | 空 | 无同步错误 | | Seconds_Behind_Source | 0 | 无同步延迟 | | Retrieved_Gtid_Set | 非空 | 已获取对端节点GTID | | Executed_Gtid_Set | 包含Retrieved_Gtid_Set | 已执行所有获取的事务 | ### 4.3 数据一致性验证 ```plain # 节点A(${NODE_A_IP})创建测试数据 docker exec -it mysql mysql -uroot -pafe123456 -h127.0.0.1 -e " CREATE DATABASE IF NOT EXISTS test_sync; USE test_sync; CREATE TABLE IF NOT EXISTS t1 (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20)); INSERT INTO t1 (name) VALUES ('nodeA_test'); " # 节点B(${NODE_B_IP})验证同步(应能查到nodeA_test数据) docker exec -it mysql mysql -uroot -pafe123456 -h127.0.0.1 -e "SELECT * FROM test_sync.t1;" # 节点B插入数据,节点A验证同步 docker exec -it mysql mysql -uroot -pafe123456 -h127.0.0.1 -e "INSERT INTO test_sync.t1 (name) VALUES ('nodeB_test');" docker exec -it mysql mysql -uroot -pafe123456 -h${NODE_A_IP} -e "SELECT * FROM test_sync.t1;" ``` ## 5. 故障处理流程 ### 5.1 宕机重启恢复 #### 场景:容器/主机重启后复制未自动恢复(两台主机操作逻辑一致,仅IP不同) ```plain # 以节点A(${NODE_A_IP})为例,恢复与节点B的复制关系 docker exec -it mysql mysql -uroot -pafe123456 -h127.0.0.1 -e " STOP REPLICA; # 重新指定复制源(利用GTID自动定位,无需手动找位点) CHANGE REPLICATION SOURCE TO SOURCE_HOST = '${NODE_B_IP}', # 对端节点IP SOURCE_PORT = 3306, SOURCE_USER = 'repl', SOURCE_PASSWORD = 'afe123456', SOURCE_AUTO_POSITION = 1, GET_SOURCE_PUBLIC_KEY = 1; START REPLICA; # 验证状态 SHOW REPLICA STATUS\G; " # 节点B执行时,仅需将SOURCE_HOST改为${NODE_A_IP}即可 ``` ### 5.2 重建节点(单节点数据损坏/丢失) #### 步骤1:从正常节点全量备份(假设节点B损坏,从节点A备份) ```plain # 节点A(${NODE_A_IP})执行备份 docker exec -it mysql mysqldump -uroot -pafe123456 -h127.0.0.1 --all-databases --master-data=2 --single-transaction > /data/mysql/backup/full_backup.sql # 将备份文件复制到损坏的节点B(${NODE_B_IP}) scp /data/mysql/backup/full_backup.sql root@${NODE_B_IP}:/data/mysql/backup/ ``` #### 步骤2:停止损坏节点并恢复数据(节点B执行) ```plain # 停止mysql容器 cd /data/mysql && docker compose down # 清空损坏数据(保留目录结构) sudo rm -rf /data/mysql/data/* # 重启mysql容器(初始化空库) docker compose up -d # 导入备份数据 docker exec -i mysql mysql -uroot -pafe123456 < /data/mysql/backup/full_backup.sql ``` #### 步骤3:重新配置双向复制 参考「3.2 配置双向复制」,重新执行节点B作为节点A从库、节点A作为节点B从库的配置命令(仅IP参数区分)。 ### 5.3 复制冲突解决 详细的冲突定位与修复流程,请参考:[MySQL 复制冲突解决指南](./fault-analysis.md) ## 6. 日志管理 详细的日志轮转与安全登录配置,请参考:[MySQL 日志管理指南](./log-management.md) ## 7. 重要提醒 ### 7.1 核心区分点(避免配置错误) - 两台主机仅 `server_id`、`report_host`(IP)、`auto_increment_offset` 三个参数不同,其余配置完全一致; - 容器名、目录路径、端口均统一为 `mysql`/`/data/mysql`/3306,通过主机IP区分节点。 ### 7.2 一致性限制 - MySQL原生异步双主复制**无法保证严格的数据一致性**,双写场景仍可能出现冲突; - 生产环境建议**单写多读**(指定一个节点为写节点,另一个为读节点),避免双写冲突。 #### 总结 #### 跨主机双主复制的核心差异仅为 `server_id`、主机IP、自增偏移量,其余目录/容器名/配置可完全统一; #### 同步故障优先通过错误日志定位冲突,修复数据而非跳过事务(避免永久不一致); #### GTID模式下无需手动定位同步位点,重启/重建节点时仅需重新指定对端IP即可自动恢复复制。