一、 读写分离介绍
1. 什么是读写分离?
读写分离是基于主从复制架构,让 主数据库处理事务性写操作 ,从数据库处理查询读操作,通过应用逻辑或中间件将读写操作路由到不同数据库服务器。
2. 为什么要读写分离?
-
提升性能:分摊数据库负载,避免读写冲突
-
提高并发:主库专注写,多个从库分担读压力
-
高可用性:从库可作为主库的故障备用
-
业务解耦:不同业务模块可使用不同的数据库连接
3. 实现方式
实现方式 | 描述 | 优点 | 缺点 |
---|---|---|---|
应用层实现 | 在代码中区分读写数据源 | 灵活可控,性能好 | 代码侵入性强,维护复杂 |
中间件代理 | 使用Mycat、ShardingSphere等中间件 | 对应用透明,集中管理 | 单点风险,增加网络跳转 |
二、 一主一从架构
1. 架构图
┌─────────────┐ 复制 ┌─────────────┐
│ 主库 Master │ ────────> │ 从库 Slave │
│ (写) │ │ (读) │
└─────────────┘ └─────────────┘
2. 配置步骤
主库配置 (my.cnf):
[mysqld]
server-id = 1
log-bin = mysql-bin
binlog-format = ROW
从库配置 (my.cnf):
[mysqld]
server-id = 2
relay-log = mysql-relay-bin
read_only = 1 # 从库只读
建立复制:
sql
-- 在主库创建复制用户
CREATE USER 'repl'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
-- 在从库配置主从连接
CHANGE MASTER TO
MASTER_HOST='master_ip',
MASTER_USER='repl',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=154;
START SLAVE;
三、 一主一从读写分离
1. 应用层实现(Spring Boot示例)
sql
# application.yml
spring:
datasource:
master:
url: jdbc:mysql://master:3306/db
username: user
password: pass
slave:
url: jdbc:mysql://slave:3306/db
username: user
password: pass
java
@Configuration
public class DataSourceConfig {
@Bean
@Primary
public DataSource dataSource() {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource());
targetDataSources.put("slave", slaveDataSource());
RoutingDataSource routingDataSource = new RoutingDataSource();
routingDataSource.setTargetDataSources(targetDataSources);
routingDataSource.setDefaultTargetDataSource(masterDataSource());
return routingDataSource;
}
}
// 使用注解切换数据源
@Service
public class UserService {
@Transactional
@DataSource("master") // 写操作使用主库
public void createUser(User user) {
userMapper.insert(user);
}
@DataSource("slave") // 读操作使用从库
public User getUserById(Long id) {
return userMapper.selectById(id);
}
}
2. 中间件实现(Mycat配置)
schema.xml:
XML
<dataHost name="host1" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="jdbc">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="jdbc:mysql://master:3306"
user="root" password="123456">
<readHost host="hostS1" url="jdbc:mysql://slave:3306"
user="root" password="123456"/>
</writeHost>
</dataHost>
balance参数说明:
-
0
:不开启读写分离 -
1
:所有读操作随机发送到所有读Host -
2
:所有读操作随机发送到所有Host(包括WriteHost) -
3
:所有读操作随机发送到所有读Host,不在writeHost的readHost上执行
四、 双主双从架构
1. 架构图
XML
┌─────────────┐ ┌─────────────┐
│ 主库1 Master1│ ←───────→ │ 主库2 Master2│
└─────────────┘ └─────────────┘
↓ ↓
┌─────────────┐ ┌─────────────┐
│ 从库1 Slave1 │ │ 从库2 Slave2 │
└─────────────┘ └─────────────┘
2. 双主配置
Master1 配置:
XML
[mysqld]
server-id = 1
log-bin = mysql-bin
auto_increment_increment = 2 # 自增步长
auto_increment_offset = 1 # 自增起始值
Master2 配置:
XML
[mysqld]
server-id = 2
log-bin = mysql-bin
auto_increment_increment = 2
auto_increment_offset = 2
配置互为主从:
sql
-- 在Master1上执行
CHANGE MASTER TO
MASTER_HOST='master2_ip',
MASTER_USER='repl',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=154;
-- 在Master2上执行
CHANGE MASTER TO
MASTER_HOST='master1_ip',
MASTER_USER='repl',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=154;
3. 双主双从优势
-
高可用:任一主库故障,另一主库可继续提供服务
-
负载均衡:写操作可分摊到两个主库
-
容灾备份:多节点数据冗余,数据更安全
五、 双主双从读写分离
1. Mycat 配置实现
schema.xml:
XML
<dataHost name="host1" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="jdbc" switchType="1">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="jdbc:mysql://master1:3306"
user="root" password="123456">
<readHost host="hostS1" url="jdbc:mysql://slave1:3306"
user="root" password="123456"/>
</writeHost>
<writeHost host="hostM2" url="jdbc:mysql://master2:3306"
user="root" password="123456">
<readHost host="hostS2" url="jdbc:mysql://slave2:3306"
user="root" password="123456"/>
</writeHost>
</dataHost>
关键参数:
-
switchType="1"
:自动切换,当写Host宕机时自动切换到其他写Host -
balance="1"
:读请求负载均衡到所有读Host
2. 应用层多数据源配置
java
@Configuration
public class MultiDataSourceConfig {
@Bean
public DataSource routingDataSource() {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master1", master1DataSource());
targetDataSources.put("master2", master2DataSource());
targetDataSources.put("slave1", slave1DataSource());
targetDataSources.put("slave2", slave2DataSource());
RoutingDataSource routingDataSource = new RoutingDataSource();
routingDataSource.setTargetDataSources(targetDataSources);
routingDataSource.setDefaultTargetDataSource(master1DataSource());
return routingDataSource;
}
// 动态数据源路由
public class ReadWriteRoutingStrategy {
private AtomicInteger masterCounter = new AtomicInteger(0);
private AtomicInteger slaveCounter = new AtomicInteger(0);
public String determineMasterLookupKey() {
int index = masterCounter.incrementAndGet() % 2;
return "master" + (index + 1);
}
public String determineSlaveLookupKey() {
int index = slaveCounter.incrementAndGet() % 2;
return "slave" + (index + 1);
}
}
}
六、 读写分离的注意事项
1. 数据一致性问题
-
复制延迟:主从同步存在毫秒级延迟
-
解决方案:
-
对实时性要求高的读操作强制走主库
-
使用半同步复制减少数据丢失风险
-
应用层根据业务容忍度选择数据源
-
2. 事务问题
java
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
// 强制使用主库,避免事务中读写分离
DataSourceContext.setDataSource("master");
try {
orderMapper.insert(order);
// 即使查询也走主库,保证事务内数据一致性
Order newOrder = orderMapper.selectById(order.getId());
// ... 其他业务逻辑
} finally {
DataSourceContext.clear();
}
}
}
3. 故障转移处理
-
监控主从复制状态
-
自动或手动切换故障节点
-
应用层重试机制
4. 负载均衡策略
-
轮询:依次分配请求
-
权重:根据服务器性能分配不同权重
-
最少连接:选择当前连接数最少的服务器
-
响应时间:选择响应时间最短的服务器
七、 监控与维护
1. 关键监控指标
sql
-- 监控主从延迟
SHOW SLAVE STATUS\G
-- 关注:Seconds_Behind_Master
-- 监控数据库连接数
SHOW STATUS LIKE 'Threads_connected';
-- 监控QPS/TPS
SHOW STATUS LIKE 'Queries';
SHOW STATUS LIKE 'Com_commit';
SHOW STATUS LIKE 'Com_rollback';
2. 日常维护命令
sql
-- 检查复制状态
SHOW REPLICA STATUS; -- MySQL 8.0+
SHOW SLAVE STATUS; -- MySQL 5.7
-- 停止/启动复制
STOP REPLICA;
START REPLICA;
-- 跳过复制错误(谨慎使用)
SET GLOBAL sql_replica_skip_counter = 1;
总结
读写分离架构演进:
sql
单机MySQL → 一主一从 → 一主多从 → 双主多从
技术选型建议:
-
小型项目:应用层实现 + 一主一从
-
中型项目:Mycat中间件 + 一主多从
-
大型项目:ShardingSphere + 双主多从 + 分库分表
核心价值 :通过读写分离,可以显著提升数据库系统的读性能 和可用性,是构建高并发应用的基础架构之一。