本文详解MySQL主从复制原理与配置,以及读写分离的实现方案,从单机到高可用架构。
前言
单机MySQL的问题:
- 单点故障
- 读写压力集中
- 无法水平扩展
主从复制是MySQL高可用的基础:
- 数据冗余,提高可用性
- 读写分离,提升性能
- 实时备份,降低风险
今天来详解MySQL主从复制的实战配置。
一、主从复制原理
1.1 复制流程
┌─────────────────────────────────────────────────────────┐
│ Master │
│ ┌─────────┐ ┌─────────────┐ │
│ │ 数据变更 │ → │ Binlog │ │
│ └─────────┘ └──────┬──────┘ │
└─────────────────────────┼────────────────────────────────┘
│ ① 传输binlog
↓
┌─────────────────────────────────────────────────────────┐
│ Slave │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ IO Thread │ → │ Relay Log │ → │ SQL Thread │ │
│ │ 接收binlog │ │ 中继日志 │ │ 回放执行 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
1.2 复制模式
| 模式 | 说明 | 优缺点 |
|---|---|---|
| 异步复制 | 主库不等从库确认 | 性能好,可能丢数据 |
| 半同步 | 至少一个从库确认 | 折中方案 |
| 组复制(MGR) | Paxos协议 | 强一致,复杂 |
二、环境准备
2.1 Docker Compose部署
yaml
# docker-compose.yml
version: '3.8'
services:
mysql-master:
image: mysql:8.0
container_name: mysql-master
environment:
MYSQL_ROOT_PASSWORD: root123
ports:
- "3306:3306"
volumes:
- ./master/conf:/etc/mysql/conf.d
- ./master/data:/var/lib/mysql
- ./master/logs:/var/log/mysql
command: --server-id=1 --log-bin=mysql-bin --binlog-format=ROW
mysql-slave:
image: mysql:8.0
container_name: mysql-slave
environment:
MYSQL_ROOT_PASSWORD: root123
ports:
- "3307:3306"
volumes:
- ./slave/conf:/etc/mysql/conf.d
- ./slave/data:/var/lib/mysql
- ./slave/logs:/var/log/mysql
command: --server-id=2 --log-bin=mysql-bin --binlog-format=ROW --read-only=1
depends_on:
- mysql-master
2.2 配置文件
Master配置:
ini
# master/conf/my.cnf
[mysqld]
server-id = 1
log-bin = mysql-bin
binlog-format = ROW
sync-binlog = 1
# 需要同步的数据库(不配则同步所有)
# binlog-do-db = mydb
# 忽略的数据库
binlog-ignore-db = mysql
binlog-ignore-db = information_schema
binlog-ignore-db = performance_schema
binlog-ignore-db = sys
# GTID模式(推荐)
gtid_mode = ON
enforce_gtid_consistency = ON
Slave配置:
ini
# slave/conf/my.cnf
[mysqld]
server-id = 2
log-bin = mysql-bin
binlog-format = ROW
relay-log = relay-bin
read-only = 1
# GTID模式
gtid_mode = ON
enforce_gtid_consistency = ON
# 跳过某些错误(谨慎使用)
# slave-skip-errors = 1062
2.3 启动服务
bash
# 创建目录
mkdir -p master/{conf,data,logs} slave/{conf,data,logs}
# 启动
docker compose up -d
# 查看状态
docker ps
三、配置主从复制
3.1 在Master创建复制用户
sql
-- 连接Master
mysql -h 127.0.0.1 -P 3306 -uroot -proot123
-- 创建复制用户
CREATE USER 'repl'@'%' IDENTIFIED BY 'repl123';
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;
-- 查看Master状态
SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000003 | 857 | | mysql,... |
+------------------+----------+--------------+------------------+
3.2 配置Slave
sql
-- 连接Slave
mysql -h 127.0.0.1 -P 3307 -uroot -proot123
-- 方式1:传统位点复制
CHANGE MASTER TO
MASTER_HOST='mysql-master',
MASTER_PORT=3306,
MASTER_USER='repl',
MASTER_PASSWORD='repl123',
MASTER_LOG_FILE='mysql-bin.000003',
MASTER_LOG_POS=857;
-- 方式2:GTID复制(推荐)
CHANGE MASTER TO
MASTER_HOST='mysql-master',
MASTER_PORT=3306,
MASTER_USER='repl',
MASTER_PASSWORD='repl123',
MASTER_AUTO_POSITION=1;
-- 启动复制
START SLAVE;
-- 查看复制状态
SHOW SLAVE STATUS\G
3.3 验证复制
sql
-- 关键字段
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 0
-- Master写入数据
CREATE DATABASE testdb;
USE testdb;
CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(50));
INSERT INTO users VALUES (1, 'test');
-- Slave查询验证
USE testdb;
SELECT * FROM users;
+----+------+
| id | name |
+----+------+
| 1 | test |
+----+------+
四、读写分离
4.1 方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 代码层面 | 简单,无额外组件 | 代码侵入 |
| 中间件 | 透明,功能丰富 | 增加组件 |
| MySQL Router | 官方支持 | 功能有限 |
4.2 代码层实现(Spring Boot)
java
// 数据源配置
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public DataSource routingDataSource(
@Qualifier("masterDataSource") DataSource master,
@Qualifier("slaveDataSource") DataSource slave) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", master);
targetDataSources.put("slave", slave);
RoutingDataSource routing = new RoutingDataSource();
routing.setTargetDataSources(targetDataSources);
routing.setDefaultTargetDataSource(master);
return routing;
}
}
// 动态数据源
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
// 数据源上下文
public class DataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();
public static void setMaster() { CONTEXT.set("master"); }
public static void setSlave() { CONTEXT.set("slave"); }
public static String getDataSource() { return CONTEXT.get(); }
public static void clear() { CONTEXT.remove(); }
}
// AOP切面
@Aspect
@Component
public class DataSourceAspect {
@Before("@annotation(readOnly)")
public void setReadDataSource(ReadOnly readOnly) {
DataSourceContextHolder.setSlave();
}
@Before("@annotation(org.springframework.transaction.annotation.Transactional)")
public void setWriteDataSource() {
DataSourceContextHolder.setMaster();
}
@After("execution(* com.example.service.*.*(..))")
public void clear() {
DataSourceContextHolder.clear();
}
}
4.3 中间件方案(ShardingSphere)
yaml
# application.yml
spring:
shardingsphere:
datasource:
names: master,slave
master:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.1.1:3306/mydb
username: root
password: root123
slave:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.1.2:3306/mydb
username: root
password: root123
rules:
readwrite-splitting:
data-sources:
readwrite_ds:
static-strategy:
write-data-source-name: master
read-data-source-names: slave
load-balancer-name: round_robin
load-balancers:
round_robin:
type: ROUND_ROBIN
五、高可用架构
5.1 MHA架构
┌─────────────────────────────────────────────────────┐
│ MHA Manager │
│ (监控+故障转移) │
└─────────────────────────────────────────────────────┘
↓ ↓ ↓
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Master │ │ Slave1 │ │ Slave2 │
│ (可写) │ │ (候选Master) │ │ (只读) │
└──────────────┘ └──────────────┘ └──────────────┘
5.2 MGR组复制
sql
-- 所有节点配置
[mysqld]
server_id = 1
gtid_mode = ON
enforce_gtid_consistency = ON
binlog_checksum = NONE
# 组复制配置
plugin_load_add = 'group_replication.so'
group_replication_group_name = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
group_replication_start_on_boot = OFF
group_replication_local_address = "192.168.1.1:33061"
group_replication_group_seeds = "192.168.1.1:33061,192.168.1.2:33061,192.168.1.3:33061"
group_replication_bootstrap_group = OFF
六、跨机房部署
6.1 场景挑战
需求:
- 主库在总部机房
- 从库在分部机房(异地灾备)
- 两个机房网络不通
传统方案:
- 专线:成本高
- 公网暴露MySQL端口:风险大
6.2 组网方案
使用组网软件(如星空组网)打通网络:
┌─────────────────────────────────────────────────────────┐
│ 组网虚拟局域网 │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ 总部机房 │ │ 分部机房 │ │
│ │ │ │ │ │
│ │ Master │ │ Slave │ │
│ │ 10.10.0.1:3306 │ ←同步─│ 10.10.0.2:3306 │ │
│ │ │ │ │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
Slave配置:
sql
-- 使用组网IP连接Master
CHANGE MASTER TO
MASTER_HOST='10.10.0.1', -- 组网IP
MASTER_PORT=3306,
MASTER_USER='repl',
MASTER_PASSWORD='repl123',
MASTER_AUTO_POSITION=1;
START SLAVE;
优势:
- 不需要公网暴露3306端口
- 加密传输,安全可靠
- 配置简单
- 运维人员可通过组网远程管理
6.3 远程运维
bash
# 通过组网IP远程连接
mysql -h 10.10.0.1 -P 3306 -uroot -p
# 远程备份
mysqldump -h 10.10.0.1 -uroot -p mydb > backup.sql
# 远程监控
mysqlsh --uri root@10.10.0.1:3306 --js
七、监控与运维
7.1 监控指标
sql
-- 复制延迟
SHOW SLAVE STATUS\G
-- Seconds_Behind_Master
-- 线程状态
SHOW PROCESSLIST;
-- 复制错误
SHOW SLAVE STATUS\G
-- Last_Error, Last_IO_Error, Last_SQL_Error
7.2 监控脚本
bash
#!/bin/bash
# check_replication.sh
MYSQL_CMD="mysql -h 127.0.0.1 -P 3307 -urepl -prepl123"
IO_RUNNING=$($MYSQL_CMD -e "SHOW SLAVE STATUS\G" | grep "Slave_IO_Running" | awk '{print $2}')
SQL_RUNNING=$($MYSQL_CMD -e "SHOW SLAVE STATUS\G" | grep "Slave_SQL_Running" | awk '{print $2}')
DELAY=$($MYSQL_CMD -e "SHOW SLAVE STATUS\G" | grep "Seconds_Behind_Master" | awk '{print $2}')
echo "IO Thread: $IO_RUNNING"
echo "SQL Thread: $SQL_RUNNING"
echo "Delay: ${DELAY}s"
if [ "$IO_RUNNING" != "Yes" ] || [ "$SQL_RUNNING" != "Yes" ]; then
echo "ALERT: Replication is broken!"
# 发送告警
fi
if [ "$DELAY" -gt 60 ]; then
echo "ALERT: Replication delay > 60s"
fi
7.3 常见问题处理
复制中断:
sql
-- 查看错误
SHOW SLAVE STATUS\G
-- 跳过错误(谨慎)
STOP SLAVE;
SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1;
START SLAVE;
-- 或使用GTID跳过
SET GTID_NEXT='xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx:N';
BEGIN; COMMIT;
SET GTID_NEXT='AUTOMATIC';
START SLAVE;
主从切换:
sql
-- 原Slave提升为Master
STOP SLAVE;
RESET SLAVE ALL;
SET GLOBAL read_only = 0;
-- 原Master降为Slave
CHANGE MASTER TO ...;
SET GLOBAL read_only = 1;
START SLAVE;
八、性能优化
8.1 并行复制
ini
# MySQL 5.7+
slave_parallel_type = LOGICAL_CLOCK
slave_parallel_workers = 4
slave_preserve_commit_order = 1
8.2 半同步复制
sql
-- Master
INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
SET GLOBAL rpl_semi_sync_master_enabled = 1;
SET GLOBAL rpl_semi_sync_master_timeout = 10000;
-- Slave
INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';
SET GLOBAL rpl_semi_sync_slave_enabled = 1;
STOP SLAVE; START SLAVE;
8.3 参数优化
ini
# binlog优化
binlog_cache_size = 4M
max_binlog_size = 500M
expire_logs_days = 7
# 复制优化
slave_net_timeout = 60
sync_relay_log = 10000
relay_log_recovery = 1
九、总结
MySQL主从复制要点:
- 基础配置:server-id唯一,开启binlog
- GTID模式:推荐使用,简化管理
- 读写分离:中间件方案更优雅
- 高可用:MHA/MGR实现自动故障转移
- 跨机房:组网打通后正常同步
- 监控告警:复制状态和延迟
生产环境清单:
☑ 主从复制配置完成
☑ 复制用户权限最小化
☑ 监控脚本部署
☑ 备份策略制定
☑ 故障切换演练
参考资料
- MySQL官方复制文档:https://dev.mysql.com/doc/refman/8.0/en/replication.html
- MySQL高可用:https://dev.mysql.com/doc/mysql-ha-scalability/en/
💡 建议:生产环境务必使用GTID模式,配置半同步复制,定期进行主从切换演练。