MySQL 运维知识点(十六)---- 读写分离
一、 读写分离介绍
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 # 从库只读
建立复制:
-- 在主库创建复制用户
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示例)
# application.yml
spring:datasource:master:url: jdbc:mysql://master:3306/dbusername: userpassword: passslave:url: jdbc:mysql://slave:3306/dbusername: userpassword: pass
@Configuration
public class DataSourceConfig {@Bean@Primarypublic 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:
<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
:所有读操作随机发送到所有读Host2
:所有读操作随机发送到所有Host(包括WriteHost)3
:所有读操作随机发送到所有读Host,不在writeHost的readHost上执行
四、 双主双从架构
1. 架构图
┌─────────────┐ ┌─────────────┐
│ 主库1 Master1│ ←───────→ │ 主库2 Master2│
└─────────────┘ └─────────────┘↓ ↓
┌─────────────┐ ┌─────────────┐
│ 从库1 Slave1 │ │ 从库2 Slave2 │
└─────────────┘ └─────────────┘
2. 双主配置
Master1 配置:
[mysqld]
server-id = 1
log-bin = mysql-bin
auto_increment_increment = 2 # 自增步长
auto_increment_offset = 1 # 自增起始值
Master2 配置:
[mysqld]
server-id = 2
log-bin = mysql-bin
auto_increment_increment = 2
auto_increment_offset = 2
配置互为主从:
-- 在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:
<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宕机时自动切换到其他写Hostbalance="1"
:读请求负载均衡到所有读Host
2. 应用层多数据源配置
@Configuration
public class MultiDataSourceConfig {@Beanpublic 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. 事务问题
@Service
public class OrderService {@Transactionalpublic void createOrder(Order order) {// 强制使用主库,避免事务中读写分离DataSourceContext.setDataSource("master");try {orderMapper.insert(order);// 即使查询也走主库,保证事务内数据一致性Order newOrder = orderMapper.selectById(order.getId());// ... 其他业务逻辑} finally {DataSourceContext.clear();}}
}
3. 故障转移处理
监控主从复制状态
自动或手动切换故障节点
应用层重试机制
4. 负载均衡策略
轮询:依次分配请求
权重:根据服务器性能分配不同权重
最少连接:选择当前连接数最少的服务器
响应时间:选择响应时间最短的服务器
七、 监控与维护
1. 关键监控指标
-- 监控主从延迟
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. 日常维护命令
-- 检查复制状态
SHOW REPLICA STATUS; -- MySQL 8.0+
SHOW SLAVE STATUS; -- MySQL 5.7-- 停止/启动复制
STOP REPLICA;
START REPLICA;-- 跳过复制错误(谨慎使用)
SET GLOBAL sql_replica_skip_counter = 1;
总结
读写分离架构演进:
单机MySQL → 一主一从 → 一主多从 → 双主多从
技术选型建议:
小型项目:应用层实现 + 一主一从
中型项目:Mycat中间件 + 一主多从
大型项目:ShardingSphere + 双主多从 + 分库分表
核心价值:通过读写分离,可以显著提升数据库系统的读性能和可用性,是构建高并发应用的基础架构之一。