【架构实战】读写分离中间件对比(ShardingSphere/MyCat)
·
一、为什么需要读写分离
在大多数互联网应用中,读操作远多于写操作:
读请求:70-80%
写请求:20-30%
单机数据库的问题:
主库:处理所有写请求 + 部分读请求
↓
连接池耗尽 → 响应变慢 → 用户投诉
解决方案:读写分离
写请求 ──► 主库
│
读请求 ──┼──► 从库1
│
└──► 从库2
二、ShardingSphere实战
1. ShardingSphere-JDBC
架构:
应用 ──► ShardingSphere-JDBC(嵌入应用)
├── 改写SQL
├── 路由到正确的数据源
└── 结果合并
│
┌────┼────┐
▼ ▼ ▼
主库 从库1 从库2
依赖配置:
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core</artifactId>
<version>5.3.0</version>
</dependency>
数据源配置:
# application.yml
spring:
shardingsphere:
datasource:
names: master,slave0,slave1
master:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.1.100:3306/order_db
username: root
password: password
slave0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.1.101:3306/order_db
username: root
password: password
slave1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://192.168.1.102:3306/order_db
username: root
password: password
rules:
readwrite_splitting:
data-sources:
ds_master_slave:
type: Static
props:
write-data-source-name: master
read-data-source-names: slave0,slave1
load-balancer:
type: ROUND_ROBIN
props:
alpha: 5
# 强制路由到主库(写操作后立即读取)
props:
query-with-connection-preference: true
Java配置方式:
@Configuration
public class ShardingSphereConfig {
@Bean
public DataSource dataSource() {
Map<String, DataSource> dataSourceMap = new HashMap<>();
// 主库
DataSource master = createDataSource(
"jdbc:mysql://192.168.1.100:3306/order_db"
);
dataSourceMap.put("master", master);
// 从库1
DataSource slave0 = createDataSource(
"jdbc:mysql://192.168.1.101:3306/order_db"
);
dataSourceMap.put("slave0", slave0);
// 从库2
DataSource slave1 = createDataSource(
"jdbc:mysql://192.168.1.102:3306/order_db"
);
dataSourceMap.put("slave1", slave1);
// 读写分离配置
ReadwriteSplittingDataSourceRuleConfiguration dataSourceConfig =
new ReadwriteSplittingDataSourceRuleConfiguration(
"ds_master_slave", // 数据源名称
"master", // 主库
Arrays.asList("slave0", "slave1"), // 从库
"round_robin" // 负载均衡策略
);
// 负载均衡配置
Properties props = new Properties();
props.setProperty("alpha", "5"); // 权重
Map<String, DataSource> dataSources = new HashMap<>();
dataSources.put("ds_master_slave",
ReadwriteSplittingDataSourceFactory.create(
dataSourceMap,
dataSourceConfig,
props
));
return dataSources.get("ds_master_slave");
}
}
强制路由到主库:
// 使用Hint强制路由到主库
HintManager hintManager = HintManager.getInstance();
hintManager.setMasterRouteOnly();
// 写完数据后立即读取,走主库
try (HintManager hintManager = HintManager.getInstance()) {
hintManager.setMasterRouteOnly();
Order order = orderService.createOrder(request);
// 这里读取走主库
return orderService.getOrder(order.getId());
}
2. ShardingSphere-Proxy
架构:
应用(不需要修改)──► MySQL Client
│
ShardingSphere-Proxy
(独立的MySQL服务进程)
│
┌──────────┼──────────┐
▼ ▼ ▼
主库 从库1 从库2
Docker部署:
# docker-compose.yml
version: '3'
services:
shardingsphere-proxy:
image: apache/shardingsphere-proxy:5.3.0
container_name: shardingsphere-proxy
ports:
- "3307:3307"
environment:
MODE_TYPE: Standalone
JVM_OPTS: "-Xmx512m -Xms512m"
volumes:
- ./conf:/opt/shardingsphere-proxy/conf
networks:
- shardingsphere
networks:
shardingsphere:
driver: bridge
server.yaml配置:
# conf/server.yaml
schemaName: readwrite_splitting
dataSources:
ds_master:
url: jdbc:mysql://192.168.1.100:3306/order_db?serverTimezone=UTC
username: root
password: password
connectionPoolClassName: com.zaxxer.hikari.HikariDataSource
maxPoolSize: 50
minPoolSize: 10
ds_slave_0:
url: jdbc:mysql://192.168.1.101:3306/order_db?serverTimezone=UTC
username: root
password: password
connectionPoolClassName: com.zaxxer.hikari.HikariDataSource
maxPoolSize: 50
minPoolSize: 10
ds_slave_1:
url: jdbc:mysql://192.168.1.102:3306/order_db?serverTimezone=UTC
username: root
password: password
connectionPoolClassName: com.zaxxer.hikari.HikariDataSource
maxPoolSize: 50
minPoolSize: 10
rules:
- !readwrite_splitting
dataSources:
prds:
type: Static
props:
write-data-source-name: ds_master
read-data-source-names: ds_slave_0,ds_slave_1
loadBalancerName: round_robin
loadBalancers:
round_robin:
type: ROUND_ROBIN
应用连接:
// 应用连接ShardingSphere-Proxy(像连接普通MySQL一样)
String url = "jdbc:mysql://192.168.1.100:3307/readwrite_splitting";
三、MyCat实战
1. MyCat安装
# 下载
wget http://dl.mycat.org.cn/2.0/Mycat-server-2.0.0-release/
tar -zxf Mycat-server-2.0.0-release.tar.gz -C /usr/local/
# 配置环境变量
export MYCAT_HOME=/usr/local/mycat
export PATH=$PATH:$MYCAT_HOME/bin
# 启动
mycat start
# 查看状态
mycat status
2. server.xml配置
<!-- conf/server.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:server SYSTEM "server.dtd">
<mycat:server xmlns:mycat="http://io.mycat/">
<system>
<property name="defaultSqlParser">druidparser</property>
<property name="serverPort">8066</property>
<property name="managerPort">9066</property>
</system>
<user name="root">
<property name="password">password</property>
<property name="schemas">order_db</property>
</user>
</mycat:server>
3. schema.xml配置
<!-- conf/schema.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="order_db" checkSQLschema="false" sqlMaxLimit="100">
<!-- 定义逻辑表(可选) -->
<table name="order" primaryKey="id" dataNode="dn1,dn2"/>
</schema>
<!-- 数据节点 -->
<dataNode name="dn1" dataHost="dh1" database="order_db"/>
<dataNode name="dn2" dataHost="dh2" database="order_db"/>
<!-- 数据主机(读写分离) -->
<dataHost name="dh1" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="native">
<heartbeat>select user()</heartbeat>
<!-- 写库 -->
<writeHost host="hostM1" url="192.168.1.100:3306"
user="root" password="password">
<!-- 读库 -->
<readHost host="hostS1" url="192.168.1.101:3306"
user="root" password="password"/>
<readHost host="hostS2" url="192.168.1.102:3306"
user="root" password="password"/>
</writeHost>
</dataHost>
</mycat:schema>
4. balance负载均衡策略
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 0 | 所有读操作发送到writeHost | 不推荐,可能读到旧数据 |
| 1 | 读操作分发到所有readHost | 常用,读写分离 |
| 2 | 随机分发到readHost | 不推荐 |
| 3 | 只分发到readHost,无可用则writeHost | 读写分离+高可用 |
推荐配置:
<dataHost name="dh1" balance="1" writeType="0">
<!-- balance=1:读操作分发到所有readHost -->
</dataHost>
四、ShardingSphere vs MyCat对比
| 维度 | ShardingSphere | MyCat |
|---|---|---|
| 架构 | JDBC层(轻量) | 独立服务(重量) |
| 部署 | 嵌入应用/独立Proxy | 独立部署 |
| 性能 | 较高 | 略低 |
| 维护 | 社区活跃 | 社区相对沉寂 |
| 分库分表 | 支持 | 支持 |
| 读写分离 | 支持 | 支持 |
| 学习成本 | 中 | 较高 |
| 配置复杂度 | 中 | 高 |
五、读写分离最佳实践
1. 什么时候用读写分离
适合场景:
- 读多写少(读:写 >= 7:3)
- 单机数据库成为瓶颈
- 数据量适中(单表<1000万)
不适合场景:
- 写多读少
- 强一致性要求(读写分离有延迟)
- 单表数据量过亿(需要分库分表)
2. 读写延迟处理
方案1:强制路由到主库
// ShardingSphere
try (HintManager hintManager = HintManager.getInstance()) {
hintManager.setMasterRouteOnly();
return orderMapper.selectById(id);
}
方案2:应用层判断
public Order getOrderAfterCreate(Long orderId) {
// 创建订单后,等待一小段时间让主从同步完成
// 然后可以走从库
Order order = orderMapper.selectById(orderId);
// 如果是刚创建的,从主库读
if (order.getCreateTime().isAfter(
LocalDateTime.now().minusSeconds(5))) {
return getOrderFromMaster(orderId);
}
return order;
}
3. 监控配置
spring:
shardingsphere:
rules:
readwrite-splitting:
data-sources:
ds_0:
type: Static
load-balancer:
type: ROUND_ROBIN
props:
# 开启SQL日志
sql-show: true
-- MyCat监控
mysql -h 192.168.1.100 -P 9066 -u root -ppassword -e "show @@datanode;"
mysql -h 192.168.1.100 -P 9066 -u root -ppassword -e "show @@heartbeat;"
六、总结
读写分离是提升数据库读性能的有效手段:
- ShardingSphere:轻量级,嵌入应用,易于使用
- MyCat:独立服务,功能强大但配置复杂
- 强制主库路由:解决读写延迟问题
- 监控告警:确保复制健康
选型建议:
- 新项目推荐ShardingSphere
- 需要分库分表优先ShardingSphere
- 遗留系统改造可选MyCat
思考题:你们用的什么读写分离方案?有没有遇到过主从延迟的问题?
个人观点,仅供参考
更多推荐



所有评论(0)