读写分离的实现方式与架构设计
概述
读写分离(Read-Write Splitting)是 MySQL 最常见的扩展架构:将写操作(INSERT/UPDATE/DELETE)发送到主库,读操作(SELECT)分发到从库,从而分散数据库负载,提升系统吞吐量。
为什么需要读写分离
单库架构:
客户端 → 一个 MySQL 实例
├── 写入
└── 读取 ← 写入和读取争抢资源
读写分离架构:
客户端
├── 写入 → 主库(写能力强)
└── 读取 → 从库 1
→ 从库 2 ← 读取分散到多个节点
→ 从库 3
核心收益:
– 读能力水平扩展(加从库即可)
– 写操作不受读查询干扰
– 主库专注于写入,性能更好
– 从库故障不影响写入
实现方式
方式一:应用层实现(代码中硬编码)
在应用程序代码中区分读写:
// Java 示例:简单的读写分离
public class DataSourceRouter {
private DataSource master; // 主库
private List<DataSource> slaves; // 从库列表
private AtomicInteger counter = new AtomicInteger(0);
public Connection getConnection(boolean isRead) {
if (isRead) {
// 路由到从库(简单轮询)
int index = counter.incrementAndGet() % slaves.size();
return slaves.get(Math.abs(index)).getConnection();
} else {
// 路由到主库
return master.getConnection();
}
}
}
// 使用
// 事务或者写操作
dataSourceRouter.getConnection(false).execute("UPDATE users SET ...");
// 读操作
dataSourceRouter.getConnection(true).execute("SELECT * FROM users ...");
优点: 简单直接,无额外中间件
缺点: 代码侵入性强,配置变更需要重新部署
方式二:框架级实现(ShardingSphere / MyCat)
通过中间件或数据库驱动实现透明的读写分离:
# ShardingSphere-JDBC 配置
spring:
shardingsphere:
datasource:
names: master,slave1,slave2
master:
url: jdbc:mysql://192.168.1.10:3306/db
slave1:
url: jdbc:mysql://192.168.1.11:3306/db
slave2:
url: jdbc:mysql://192.168.1.12:3306/db
rules:
readwrite-splitting:
data-sources:
rw:
write-data-source-name: master
read-data-source-names:
- slave1
- slave2
load-balancer: round_robin
优点: 无代码侵入,配置灵活
缺点: 增加中间件运维成本
方式三:代理层实现(Proxy)
独立的数据库代理服务(如 ProxySQL、MySQL Router):
# ProxySQL 配置
mysql_servers:
- hostgroup: 0 # 写组
address: 192.168.1.10
port: 3306
- hostgroup: 1 # 读组
address: 192.168.1.11
port: 3306
- hostgroup: 1
address: 192.168.1.12
port: 3306
mysql_query_rules:
- rule_id: 1
active: 1
match_pattern: "^SELECT"
destination_hostgroup: 1 # SELECT 到从库
cache_ttl: 1000
- rule_id: 2
active: 1
match_pattern: "^.*"
destination_hostgroup: 0 # 其他到主库
客户端 → ProxySQL(3306)
│
├── SELECT → 从库池(轮询/权重)
│
└── INSERT/UPDATE/DELETE → 主库
优点: 对应用完全透明
缺点: 增加网络跳数,代理自身也有性能开销
三种方式对比
| 方式 | 复杂度 | 代码侵入 | 性能损耗 | 运维成本 | 灵活性 |
|---|---|---|---|---|---|
| 应用层 | 低 | 高 | 无 | 低 | 低 |
| 框架层 | 中 | 低 | 中 | 中 | 中 |
| 代理层 | 高 | 无 | 低 | 高 | 高 |
读负载均衡策略
| 策略 | 描述 | 适用场景 |
|---|---|---|
| Round Robin | 轮询,每个从库轮流接收读请求 | 从库配置相同 |
| Least Connections | 请求发到连接数最少的从库 | 从库负载不均 |
| 权重分配 | 按权重分配(配置高的从库权重高) | 异构从库 |
| 随机 | 随机选择 | 简单场景 |
事务内的读写
重要原则:事务内所有操作应该走同一个数据源。
// 错误做法
@Transactional
public void updateAndQuery() {
// 写操作走主库
userMapper.updateBalance(1, 100);
// 读操作可能走到从库
User user = userMapper.selectById(1);
// ❌ 从库可能还没同步到新数据!
}
// 正确做法:事务强制走主库
@Transactional
public void updateAndQuery() {
userMapper.updateBalance(1, 100);
// 强制使用主库读取
HintManager.getInstance().setWriteRouteOnly();
User user = userMapper.selectById(1);
// ✅ 读写都在主库,数据一致
}
面试要点
- 读写分离的核心:写主库(一个)、读从库(多个),分散读压力
- 三种实现方式:应用层硬编码(最简单)、框架层 ShardingSphere(最常用)、代理层 ProxySQL(最透明)
- 事务内强制走主库:避免”写后读”读到旧数据
- 读负载均衡:轮询、最少连接、权重三种主要策略
- 读写分离不能解决写瓶颈:写操作仍然集中在主库
- MySQL Router 是官方方案:简单场景推荐 MySQL Router + 从库启用插件
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END


暂无评论内容