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的数据同步机制
- 配置管理:主从库的详细配置参数
- 监控告警:实时监控复制状态和延迟
- 故障处理:常见问题的诊断和解决
- 架构设计:读写分离、故障切换等高可用方案
掌握这些知识,能够帮助你在实际项目中设计出稳定、高效的数据库架构,为业务系统提供可靠的数据存储保障。