Initial commit with project setup and basic structure established.

This commit is contained in:
2026-01-16 17:44:35 +08:00
parent bf3ab6a241
commit 02bf619c65
40 changed files with 3363 additions and 0 deletions
@@ -0,0 +1,337 @@
# 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 = 86400,
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 = 86400,
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即可自动恢复复制。
@@ -0,0 +1,76 @@
# MySQL 8.4 复制冲突解决指南
> **AI 响应规范 (MySQL 执行策略)**:
> 1. **强制参数**: 所有 `docker exec` 命令必须包含 `-h127.0.0.1` 参数。
> 2. **双重输出**: 涉及 SQL 操作时,必须同时输出 `Docker 执行命令` 和 `纯 SQL 脚本`。
> 3. **默认连接**: 默认使用 `-uroot -pafe123456 -h127.0.0.1`。
## 1. 复制冲突解决(主键/唯一键重复)
### 1.1 快速定位冲突信息
提取冲突 GTID、表、值(直接执行,自动解析错误日志):
```bash
grep "Duplicate entry" /data/mysql/logs/mysql_error.log | perl -nle '
if (/^(\S+).*?transaction\s+'\''([^'\'']+)'\''.*?table\s+([^\s;]+);.*?Duplicate entry\s+'\''([^'\'']+)'\''\s+for\s+key\s+'\''([^'\'']+)'\''/) {
print "\n" . "="x50;
print "时间: $1";
print "GTID: $2";
print "表名: $3";
print "冲突值: $4 (索引: $5)";
print "\n方案 A (删除冲突行):";
print "DELETE FROM $3 WHERE [主键列] = \x27$4\x27;";
print "\n方案 B (跳过此事务):";
print "STOP REPLICA; SET GTID_NEXT=\x27$2\x27; BEGIN; COMMIT; SET GTID_NEXT=\x27AUTOMATIC\x27; START REPLICA;";
}
'
```
### 1.2 安全修复冲突(推荐删除冲突数据)
停止复制,临时关闭 binlog(避免删除操作同步到对端):
**A. Docker 执行模式 (包含 -h127.0.0.1):**
```bash
# 以实际冲突表和主键为例(示例:test_sync.t1 的 id=12)
docker exec -it mysql mysql -uroot -pafe123456 -h127.0.0.1 -e "
STOP REPLICA;
SET sql_log_bin = OFF;
DELETE FROM `test_sync`.`t1` WHERE `id` = 12;
SET sql_log_bin = ON;
START REPLICA;
SHOW REPLICA STATUS\G;
"
```
**B. 纯 SQL 模式:**
```sql
STOP REPLICA;
SET sql_log_bin = OFF;
DELETE FROM `test_sync`.`t1` WHERE `id` = 12;
SET sql_log_bin = ON;
START REPLICA;
SHOW REPLICA STATUS\G;
```
### 1.3 应急跳过冲突事务(仅紧急场景)
精准跳过指定 GTID 事务(MySQL 8.0+ 推荐):
**A. Docker 执行模式 (包含 -h127.0.0.1):**
```bash
docker exec -it mysql mysql -uroot -pafe123456 -h127.0.0.1 -e "
STOP REPLICA;
SET GTID_NEXT = '760f0600-f896-11ef-b265-0242ac130002:6'; # 替换为实际冲突 GTID
BEGIN; COMMIT;
SET GTID_NEXT = 'AUTOMATIC';
START REPLICA;
"
```
**B. 纯 SQL 模式:**
```sql
STOP REPLICA;
SET GTID_NEXT = '760f0600-f896-11ef-b265-0242ac130002:6';
BEGIN; COMMIT;
SET GTID_NEXT = 'AUTOMATIC';
START REPLICA;
```
@@ -0,0 +1,89 @@
# MySQL 8.4 日志管理指南
> **AI 响应规范 (MySQL 执行策略)**:
> 1. **强制参数**: 所有 `docker exec` 命令必须包含 `-h127.0.0.1` 参数。
> 2. **双重输出**: 涉及 SQL 操作时,必须同时输出 `Docker 执行命令` 和 `纯 SQL 脚本`。
## 1. 日志轮转配置
防止日志文件过大,两台主机配置一致。创建 `/etc/logrotate.d/mysql_error_log`:
```plain
/data/mysql/logs/mysql_error.log /data/mysql/logs/mysql_slow.log {
daily # 每天轮转
missingok # 文件不存在不报错
rotate 30 # 保留30天日志
compress # 压缩旧日志
delaycompress # 延迟压缩(避免写入异常)
notifempty # 空文件不轮转
create 644 # 新日志权限
postrotate
# 刷新MySQL日志文件
docker exec mysql mysql -uroot -pafe123456 -h127.0.0.1 -e "FLUSH ERROR LOGS; FLUSH SLOW LOGS;" || true
endscript
}
```
### 1.1 手动测试日志轮转
```bash
logrotate -f /etc/logrotate.d/mysql_error_log
```
## 2. 加密登录配置(.mylogin.cnf)
### 2.1 配置目的
为避免执行日志刷新操作时明文暴露数据库账号密码,通过 `mysql_config_editor` 生成加密的 `.mylogin.cnf` 文件,实现安全、免密的日志操作。
### 2.2 操作步骤
#### 步骤 1:在宿主机器生成加密登录配置
直接在宿主机器执行以下命令(两台双主节点均需执行):
```bash
mysql_config_editor set \
--login-path=mysql_log_flush \ # 自定义标识
--user=log_flush \ # 提前创建的日志操作专用低权限账号
--password \ # 交互式输入密码
--host=127.0.0.1 \ # 本地连接地址
--port=3306 # MySQL 服务端口
```
#### 步骤 2:将配置文件同步至 MySQL 容器
有两种实现方式:
**方式 1:复制配置文件到容器内**
```bash
sudo docker cp ~/.mylogin.cnf mysql:/root/.mylogin.cnf
```
**方式 2:执行命令时临时挂载配置文件**
```bash
docker exec -v ~/.mylogin.cnf:/root/.mylogin.cnf mysql \
mysql --login-path=mysql_log_flush -e "FLUSH ERROR LOGS; FLUSH SLOW LOGS;"
```
#### 步骤 3:使用加密配置执行日志操作
```bash
docker exec mysql mysql --login-path=mysql_log_flush -e "FLUSH ERROR LOGS; FLUSH SLOW LOGS;"
```
### 2.3 安全与权限注意事项
1. **专用账号权限最小化**:仅授予 `RELOAD` 权限。
**A. Docker 执行模式:**
```bash
docker exec -it mysql mysql -uroot -pafe123456 -h127.0.0.1 -e "
CREATE USER 'log_flush'@'127.0.0.1' IDENTIFIED BY 'afe123456';
GRANT RELOAD ON *.* TO 'log_flush'@'127.0.0.1';
FLUSH PRIVILEGES;
"
```
**B. 纯 SQL 模式:**
```sql
CREATE USER 'log_flush'@'127.0.0.1' IDENTIFIED BY 'afe123456';
GRANT RELOAD ON *.* TO 'log_flush'@'127.0.0.1';
FLUSH PRIVILEGES;
```
2. **配置文件权限管控**:`.mylogin.cnf` 权限应为 600。
3. **密码更新处理**:若密码修改,需重新生成配置。