数据库如同现代业务的心脏,一旦停跳,整个系统将陷入瘫痪。想象一个双十一场景:每秒数十万请求涌向数据库,如何保证服务不崩溃?主从复制与读写分离正是高可用架构的核心利器。主从复制让数据在多个节点间实时同步,即使主库宕机,从库也能无缝顶替;读写分离则将查询压力分散,显著提升吞吐量。本文将用MySQL实战演示主从复制的搭建过程,并通过Java代码实现读写分离逻辑。无论您是面对高并发挑战的架构师,还是初探高可用的开发者,都能在本文中找到可落地的解决方案。
数据同步流程
-- 主从复制核心机制示意图
Master: [binlog] → 网络 → Slave: [IO Thread → Relay Log → SQL Thread → 数据落盘]
核心优势
/etc/mysql/my.cnf
)[mysqld]
server_id=1 # 唯一ID,主从不能重复
log_bin=mysql-bin # 启用binlog
binlog_format=ROW # 推荐使用ROW格式
CREATE USER 'repl'@'%' IDENTIFIED BY 'SecurePass123!';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;
FLUSH TABLES WITH READ LOCK;
SHOW MASTER STATUS; -- 记录File和Position(例如:mysql-bin.000001, 107)
[mysqld]
server_id=2 # 不同于主库的ID
relay_log=relay-bin
CHANGE MASTER TO
MASTER_HOST='master_ip',
MASTER_USER='repl',
MASTER_PASSWORD='SecurePass123!',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=107;
START SLAVE;
SHOW SLAVE STATUS\G
-- 关键指标:Slave_IO_Running=Yes, Slave_SQL_Running=Yes
# 模拟主库宕机
sudo systemctl stop mysql@master
# 将从库提升为主库
STOP SLAVE;
RESET SLAVE ALL;
# 修改应用配置指向新主库
public class ReadWriteRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
// 通过ThreadLocal标识读写请求
return TransactionContext.isReadOnly() ? "read" : "write";
}
}
@Configuration
public class DataSourceConfig {
@Bean
public DataSource masterDataSource() {
return DataSourceBuilder.create()
.url("jdbc:mysql://master_ip:3306/db")
.username("admin").password("pass")
.build();
}
@Bean
public DataSource slaveDataSource() {
return DataSourceBuilder.create()
.url("jdbc:mysql://slave_ip:3306/db")
.username("admin").password("pass")
.build();
}
@Bean
public DataSource routingDataSource(
@Qualifier("masterDataSource") DataSource master,
@Qualifier("slaveDataSource") DataSource slave) {
ReadWriteRoutingDataSource routingDataSource = new ReadWriteRoutingDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("write", master);
targetDataSources.put("read", slave);
routingDataSource.setTargetDataSources(targetDataSources);
return routingDataSource;
}
}
@Transactional(readOnly = true) // 标记为读请求,路由到从库
public List<User> findActiveUsers() {
return jdbcTemplate.query("SELECT * FROM users WHERE active=1", ...);
}
@Transactional // 默认写请求,路由到主库
public void createUser(User user) {
jdbcTemplate.update("INSERT INTO users VALUES(?,?,?)", ...);
}
特性 | 主从复制 | 读写分离 |
---|---|---|
核心目标 | 数据冗余与灾备 | 提升读性能与系统吞吐量 |
延迟问题 | 毫秒~秒级数据延迟 | 依赖主从复制延迟 |
适用场景 | 备份恢复、数据分析库 | 高并发读业务(电商、社交) |
复杂性 | 中等(需同步监控) | 高(需中间件/代码层支持) |
-- 使用半同步复制(主库至少收到一个从库ACK才提交)
INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
SET GLOBAL rpl_semi_sync_master_enabled=ON;
# 使用Keepalived实现VIP漂移
vrrp_script chk_mysql { script "/usr/bin/mysqlchk" }
@Transactional(readOnly = true)
public void updateUser() {
// Spring会抛出ReadOnlyException
jdbcTemplate.update("UPDATE users...");
}
当每秒10万查询压向数据库时,主从复制与读写分离不再只是技术选项,而是生存必需。但真正的挑战在于:如何平衡数据一致性与性能?当您的从库延迟飙升时,是优先保证数据准确性还是接受短暂脏读?您在实战中是否遇到过主从切换导致的数据丢失陷阱?欢迎分享您在架构演进中踩过的深坑或创新方案——您的经验将照亮更多开发者的高可用之路!## MySQL高可用架构实战:主从复制与读写分离的终极指南
数据库如同现代业务的心脏,一旦停跳,整个系统将陷入瘫痪。想象一个双十一场景:每秒数十万请求涌向数据库,如何保证服务不崩溃?主从复制与读写分离正是高可用架构的核心利器。主从复制让数据在多个节点间实时同步,即使主库宕机,从库也能无缝顶替;读写分离则将查询压力分散,显著提升吞吐量。本文将用MySQL实战演示主从复制的搭建过程,并通过Java代码实现读写分离逻辑。无论您是面对高并发挑战的架构师,还是初探高可用的开发者,都能在本文中找到可落地的解决方案。
数据同步流程
-- 主从复制核心机制示意图
Master: [binlog] → 网络 → Slave: [IO Thread → Relay Log → SQL Thread → 数据落盘]
核心优势
/etc/mysql/my.cnf
)[mysqld]
server_id=1 # 唯一ID,主从不能重复
log_bin=mysql-bin # 启用binlog
binlog_format=ROW # 推荐使用ROW格式
CREATE USER 'repl'@'%' IDENTIFIED BY 'SecurePass123!';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;
FLUSH TABLES WITH READ LOCK;
SHOW MASTER STATUS; -- 记录File和Position(例如:mysql-bin.000001, 107)
[mysqld]
server_id=2 # 不同于主库的ID
relay_log=relay-bin
CHANGE MASTER TO
MASTER_HOST='master_ip',
MASTER_USER='repl',
MASTER_PASSWORD='SecurePass123!',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=107;
START SLAVE;
SHOW SLAVE STATUS\G
-- 关键指标:Slave_IO_Running=Yes, Slave_SQL_Running=Yes
# 模拟主库宕机
sudo systemctl stop mysql@master
# 将从库提升为主库
STOP SLAVE;
RESET SLAVE ALL;
# 修改应用配置指向新主库
public class ReadWriteRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
// 通过ThreadLocal标识读写请求
return TransactionContext.isReadOnly() ? "read" : "write";
}
}
@Configuration
public class DataSourceConfig {
@Bean
public DataSource masterDataSource() {
return DataSourceBuilder.create()
.url("jdbc:mysql://master_ip:3306/db")
.username("admin").password("pass")
.build();
}
@Bean
public DataSource slaveDataSource() {
return DataSourceBuilder.create()
.url("jdbc:mysql://slave_ip:3306/db")
.username("admin").password("pass")
.build();
}
@Bean
public DataSource routingDataSource(
@Qualifier("masterDataSource") DataSource master,
@Qualifier("slaveDataSource") DataSource slave) {
ReadWriteRoutingDataSource routingDataSource = new ReadWriteRoutingDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("write", master);
targetDataSources.put("read", slave);
routingDataSource.setTargetDataSources(targetDataSources);
return routingDataSource;
}
}
@Transactional(readOnly = true) // 标记为读请求,路由到从库
public List<User> findActiveUsers() {
return jdbcTemplate.query("SELECT * FROM users WHERE active=1", ...);
}
@Transactional // 默认写请求,路由到主库
public void createUser(User user) {
jdbcTemplate.update("INSERT INTO users VALUES(?,?,?)", ...);
}
特性 | 主从复制 | 读写分离 |
---|---|---|
核心目标 | 数据冗余与灾备 | 提升读性能与系统吞吐量 |
延迟问题 | 毫秒~秒级数据延迟 | 依赖主从复制延迟 |
适用场景 | 备份恢复、数据分析库 | 高并发读业务(电商、社交) |
复杂性 | 中等(需同步监控) | 高(需中间件/代码层支持) |
-- 使用半同步复制(主库至少收到一个从库ACK才提交)
INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
SET GLOBAL rpl_semi_sync_master_enabled=ON;
# 使用Keepalived实现VIP漂移
vrrp_script chk_mysql { script "/usr/bin/mysqlchk" }
@Transactional(readOnly = true)
public void updateUser() {
// Spring会抛出ReadOnlyException
jdbcTemplate.update("UPDATE users...");
}
当每秒10万查询压向数据库时,主从复制与读写分离不再只是技术选项,而是生存必需。但真正的挑战在于:如何平衡数据一致性与性能?当您的从库延迟飙升时,是优先保证数据准确性还是接受短暂脏读?您在实战中是否遇到过主从切换导致的数据丢失陷阱?欢迎分享您在架构演进中踩过的深坑或创新方案——您的经验将照亮更多开发者的高可用之路!