1. 引言:主从同步的价值与意义
在现代分布式系统中,数据库主从同步是构建高可用、高性能架构的基石。对于Java开发者而言,深入理解主从同步机制有助于:
- 读写分离:提升系统吞吐量
- 数据备份:保证数据安全性
- 故障恢复:实现快速故障转移
- 负载均衡:分散数据库压力
// 典型的主从架构应用场景
@Service
public class OrderService {
@Autowired
private DataSource routingDataSource;
public void createOrder(Order order) {
// 写操作路由到主库
try (Connection conn = routingDataSource.getWriteConnection()) {
orderDAO.insert(conn, order);
}
}
public Order getOrder(Long orderId) {
// 读操作路由到从库
try (Connection conn = routingDataSource.getReadConnection()) {
return orderDAO.selectById(conn, orderId);
}
}
}
2. 主从同步架构概览
2.1 核心组件与数据流向

2.2 三种复制模式对比
|
复制模式 |
数据一致性 |
性能 |
网络要求 |
适用场景 |
|
异步复制 |
弱一致性 |
最高 |
低 |
读多写少,可容忍数据延迟 |
|
半同步复制 |
强一致性 |
中等 |
中 |
金融交易,要求数据安全 |
|
全同步复制 |
强一致性 |
最低 |
高 |
强一致性要求的核心业务 |
3. 二进制日志(Binlog)深入解析
3.1 Binlog的三种格式
-- 查看当前Binlog格式 SHOW VARIABLES LIKE 'binlog_format'; -- 不同格式的配置示例 SET GLOBAL binlog_format = 'ROW'; -- 行格式,数据安全 SET GLOBAL binlog_format = 'STATEMENT'; -- 语句格式,性能好 SET GLOBAL binlog_format = 'MIXED'; -- 混合格式,平衡选择
格式对比分析:
public class BinlogFormatComparison {
/**
* STATEMENT格式示例
* 记录:UPDATE users SET status = 1 WHERE create_time < '2023-01-01'
* 问题:主从时间不一致可能导致数据不一致
*/
public void statementFormatIssue() {
// 主库执行时间:2023-01-01 10:00:00
// 从库执行时间:2023-01-01 09:59:59
// 结果:影响的行数可能不同!
}
/**
* ROW格式示例
* 记录:user_id=1 status=1, user_id=2 status=1, ...
* 优点:精确到行变化,数据一致性强
*/
public void rowFormatAdvantage() {
// 记录每一行数据的变化,不依赖执行环境
// 保证主从数据完全一致
}
}
3.2 Binlog事件类型详解
-- 查看Binlog事件 SHOW BINLOG EVENTS IN 'mysql-bin.000001' LIMIT 10; -- 输出示例: -- | Log_name | Pos | Event_type | Server_id | End_log_pos | Info | -- | mysql-bin.000001 | 4 | Format_desc | 1 | 123 | ... | -- | mysql-bin.000001 | 123 | Previous_gtids | 1 | 154 | ... | -- | mysql-bin.000001 | 154 | Gtid | 1 | 219 | ... | -- | mysql-bin.000001 | 219 | Query | 1 | 313 | BEGIN | -- | mysql-bin.000001 | 313 | Table_map | 1 | 366 | ... | -- | mysql-bin.000001 | 366 | Update_rows | 1 | 416 | ... | -- | mysql-bin.000001 | 416 | Xid | 1 | 447 | COMMIT |
4. 主从同步配置实战
4.1 主库配置详解
# /etc/my.cnf 主库配置 [mysqld] # 服务器标识,集群内唯一 server-id = 1 # Binlog配置 log-bin = mysql-bin binlog-format = ROW expire_logs_days = 7 max_binlog_size = 100M # 复制过滤(可选) binlog-do-db = business_db # 只复制指定数据库 # binlog-ignore-db = mysql # 忽略系统库 # 半同步复制配置(如使用) plugin-load = "rpl_semi_sync_master=semisync_master.so" rpl_semi_sync_master_enabled = 1
创建复制用户:
sql
-- 创建专门用于复制的用户 CREATE USER 'repl'@'%' IDENTIFIED BY 'SecurePassword123!'; GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%'; FLUSH PRIVILEGES; -- 查看主库状态,记录File和Position SHOW MASTER STATUS; -- 输出: -- +------------------+----------+--------------+------------------+-------------------+ -- | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | -- +------------------+----------+--------------+------------------+-------------------+ -- | mysql-bin.000003 | 785 | | | | -- +------------------+----------+--------------+------------------+-------------------+
4.2 从库配置详解
ini
# /etc/my.cnf 从库配置 [mysqld] server-id = 2 relay-log = mysql-relay-bin read-only = 1 super-read-only = 1 # 复制过滤(可选) replicate-do-db = business_db # replicate-ignore-db = mysql # 从库性能优化 slave_parallel_workers = 4 slave_parallel_type = LOGICAL_CLOCK # 半同步复制配置(如使用) plugin-load = "rpl_semi_sync_slave=semisync_slave.so" rpl_semi_sync_slave_enabled = 1
配置复制链路:
sql
-- 停止从库复制 STOP SLAVE; -- 配置主库连接信息 CHANGE MASTER TO MASTER_HOST = '192.168.1.100', MASTER_USER = 'repl', MASTER_PASSWORD = 'SecurePassword123!', MASTER_PORT = 3306, MASTER_LOG_FILE = 'mysql-bin.000003', MASTER_LOG_POS = 785, MASTER_CONNECT_RETRY = 60, MASTER_RETRY_COUNT = 86400; -- 启动从库复制 START SLAVE; -- 检查复制状态 SHOW SLAVE STATUS\G
5. 基于GTID的复制
5.1 GTID原理与优势
GTID(Global Transaction Identifier)为每个事务分配全局唯一ID,简化主从切换和故障恢复。
sql
-- 启用GTID复制 -- 主从库都需要在my.cnf中配置: -- gtid_mode = ON -- enforce_gtid_consistency = ON -- 基于GTID的复制配置 STOP SLAVE; CHANGE MASTER TO MASTER_HOST = '192.168.1.100', MASTER_USER = 'repl', MASTER_PASSWORD = 'SecurePassword123!', MASTER_AUTO_POSITION = 1; START SLAVE;
5.2 GTID结构与应用
java
public class GTIDUnderstanding {
/**
* GTID格式:source_id:transaction_id
* 示例:3E11FA47-71CA-11E1-9E33-C80AA9429562:1-5
*
* 优势:
* 1. 自动定位复制位置,无需记录binlog文件和position
* 2. 故障切换更简单
* 3. 支持多源复制
*/
public void demonstrateGTIDAdvantage() {
// 传统复制:需要精确的binlog文件和position
String masterLogFile = "mysql-bin.000003";
long masterLogPos = 785;
// GTID复制:只需知道最后执行的GTID
String lastGTID = "3E11FA47-71CA-11E1-9E33-C80AA9429562:1-100";
// 从库会自动从101开始复制
}
}
6. 复制监控与故障排查
6.1 关键监控指标
sql
-- 查看从库状态详情 SHOW SLAVE STATUS\G -- 关键字段解读: -- Slave_IO_Running: Yes -- I/O线程运行状态 -- Slave_SQL_Running: Yes -- SQL线程运行状态 -- Seconds_Behind_Master: 0 -- 复制延迟秒数 -- Last_IO_Errno: 0 -- 最后I/O错误码 -- Last_SQL_Errno: 0 -- 最后SQL错误码 -- Retrieved_Gtid_Set: ... -- 已接收的GTID集合 -- Executed_Gtid_Set: ... -- 已执行的GTID集合
6.2 常见问题与解决方案
6.2.1 主键冲突处理
sql
-- 错误信息:Duplicate entry '123' for key 'PRIMARY' -- 解决方案1:跳过错误(谨慎使用) STOP SLAVE; SET GLOBAL sql_slave_skip_counter = 1; START SLAVE; -- 解决方案2:删除冲突数据(确保数据一致性) STOP SLAVE; -- 在从库删除冲突记录 DELETE FROM table_name WHERE id = 123; START SLAVE;
6.2.2 复制延迟优化
java
// 复制延迟的Java监控方案
@Component
public class ReplicationMonitor {
@Autowired
private JdbcTemplate jdbcTemplate;
public boolean checkReplicationHealth() {
try {
Map<String, Object> status = jdbcTemplate.queryForMap(
"SHOW SLAVE STATUS");
String ioRunning = (String) status.get("Slave_IO_Running");
String sqlRunning = (String) status.get("Slave_SQL_Running");
Long secondsBehind = (Long) status.get("Seconds_Behind_Master");
return "Yes".equals(ioRunning) &&
"Yes".equals(sqlRunning) &&
secondsBehind < 10; // 延迟小于10秒
} catch (Exception e) {
return false;
}
}
public void handleReplicationDelay() {
// 优化策略:
// 1. 增加slave_parallel_workers
// 2. 优化大表查询
// 3. 升级从库硬件
// 4. 使用多线程复制
}
}
6.3 性能优化配置
ini
# 从库性能优化配置 [mysqld] # 多线程复制 slave_parallel_workers = 8 slave_parallel_type = LOGICAL_CLOCK # 内存优化 slave_pending_jobs_size_max = 128M # 网络优化 slave_net_timeout = 60 # 日志优化 sync_relay_log = 1000 sync_relay_log_info = 1000
7. 高可用架构实践
7.1 主从切换策略
java
@Service
public class FailoverService {
@Autowired
private DataSourceManager dataSourceManager;
public void performFailover(MasterSlaveConfig config) {
try {
// 1. 停止应用写入
stopApplicationWrites();
// 2. 确保从库追上主库
waitForSlaveCatchUp(config.getSlaveDataSource());
// 3. 将从库设置为可写
promoteSlaveToMaster(config.getSlaveDataSource());
// 4. 切换数据源配置
dataSourceManager.switchToNewMaster(config.getSlaveHost());
// 5. 启动应用写入
startApplicationWrites();
} catch (Exception e) {
// 回滚策略
rollbackFailover();
}
}
private void waitForSlaveCatchUp(DataSource slaveDataSource) {
JdbcTemplate jdbc = new JdbcTemplate(slaveDataSource);
while (true) {
Long secondsBehind = jdbc.queryForObject(
"SELECT Seconds_Behind_Master FROM information_schema.slave_status",
Long.class);
if (secondsBehind == 0) {
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
7.2 读写分离实现
java
@Configuration
public class ReadWriteRoutingConfiguration {
@Bean
public DataSource dataSource() {
Map<Object, Object> dataSourceMap = new HashMap<>();
// 主库(写)
dataSourceMap.put("master", createMasterDataSource());
// 从库(读)
dataSourceMap.put("slave1", createSlaveDataSource());
dataSourceMap.put("slave2", createSlaveDataSource());
// 路由配置
ReadWriteRoutingDataSource routingDataSource =
new ReadWriteRoutingDataSource();
routingDataSource.setDefaultTargetDataSource(dataSourceMap.get("master"));
routingDataSource.setTargetDataSources(dataSourceMap);
return routingDataSource;
}
@Bean
@Primary
public LazyConnectionDataSourceProxy lazyDataSource(DataSource dataSource) {
return new LazyConnectionDataSourceProxy(dataSource);
}
}
// 自定义路由数据源
public class ReadWriteRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return TransactionSynchronizationManager.isCurrentTransactionReadOnly()
? "slave" : "master";
}
}
8. 生产环境最佳实践
8.1 监控告警体系
yaml
# Prometheus监控配置示例
scrape_configs:
- job_name: 'mysql_replication'
static_configs:
- targets: ['mysql-slave:9104']
metrics_path: /metrics
params:
collect[]:
- replication
- engine_innodb
- global_status
# 关键告警规则
groups:
- name: mysql_replication
rules:
- alert: MySQLReplicationNotRunning
expr: mysql_slave_status_slave_io_running == 0 or mysql_slave_status_slave_sql_running == 0
for: 1m
labels:
severity: critical
annotations:
summary: "MySQL复制停止运行"
- alert: MySQLReplicationLag
expr: mysql_slave_status_seconds_behind_master > 30
for: 5m
labels:
severity: warning
annotations:
summary: "MySQL复制延迟超过30秒"
8.2 备份与恢复策略
sql
-- 基于复制的备份方案 -- 1. 在从库上进行备份,不影响主库性能 -- 2. 使用mysqldump或物理备份工具 -- 示例备份脚本 -- mysqldump -h slave_host -u backup_user -p business_db > backup.sql -- 恢复时重新搭建复制 CHANGE MASTER TO ...; START SLAVE;
9. 总结
MySQL主从同步是构建高可用数据库架构的核心技术,Java开发者需要深入理解:
- 复制原理:基于Binlog的数据同步机制
- 配置管理:主从库的详细配置参数
- 监控告警:实时监控复制状态和延迟
- 故障处理:常见问题的诊断和解决
- 架构设计:读写分离、故障切换等高可用方案
掌握这些知识,能够帮助你在实际项目中设计出稳定、高效的数据库架构,为业务系统提供可靠的数据存储保障。
488

被折叠的 条评论
为什么被折叠?



