之前的文章,对架构层面的知识体系,有一个全面一点的介绍
从nginx做集群
到gateway做集群
到拆分微服务,给jar包做集群
在到缓存做集群
给中间件做集群
给存储系统做集群
在引入k8s
在每个架构领域节点,实现三高
从而实现整体的三高
包括,高性能,高可用,高并发
就是响应快,不会挂,一次能接收大量的数据
这里我们这个文章,介绍一下存储领域,mysql架构层面,实现三高的思路
我们这里讲一下解决这三个问题的,出的解决方案
我们在引入两个概念,读写分离和分库分表
首先说一下,为什么需要读写分离
我们知道单机的mysql性能有瓶颈
于是,我们就可以横向的拓展mysql
增加mysql的主机
又因为,从业务领域来看,查询是mysql的主要业务,
我们限定了一些机器只做查询
进而增加了mysql的高可用,高性能,和高并发
因为增加了机器,从整体上,肯定实现了这个效果
当然,选用中间件,缓存,在不同的场景下,更合适
我们如何实现读写分离:
你提到的"配置好了"可能是指两个 MySQL 的主从复制已经设置完成。这是实现读写分离的数据基础,确保写入主库的数据能同步到从库。
在此基础上,Spring Boot 应用层需要做的,是将"读"和"写"两类 SQL 请求,路由到不同的数据库。这在 Java 生态里有几种成熟的实现方式。
⚙️ 方案一:Spring 原生方案(AbstractRoutingDataSource + AOP)
这是最经典、侵入性较低的方式,通过在应用层动态决定使用哪个数据源。
1. 添加依赖
确保 pom.xml 中有 Spring Boot Starter JDBC 和 MyBatis 相关依赖。
2. 配置数据源 (application.yml)
分别配置主库(写)和从库(读)的连接信息。
yaml
spring:
datasource:
master:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://<你的写库DockerIP>:3306/<数据库名>?useSSL=false&serverTimezone=UTC
username: root
password: <你的写库密码>
slave:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://<你的读库DockerIP>:3306/<数据库名>?useSSL=false&serverTimezone=UTC
username: root
password: <你的读库密码>
注意:如果两个 MySQL 在 Docker 的不同容器中,Spring Boot 应用要通过容器所在宿主机的 IP 和映射端口来访问。
3. 创建数据源上下文 (DataSourceContextHolder)
使用 ThreadLocal 保存当前线程的数据源标识。
java
public class DataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDataSourceType(String dataSourceType) {
CONTEXT_HOLDER.set(dataSourceType);
}
public static String getDataSourceType() {
return CONTEXT_HOLDER.get();
}
public static void clearDataSourceType() {
CONTEXT_HOLDER.remove();
}
}
4. 创建动态数据源 (DynamicDataSource)
继承 AbstractRoutingDataSource,实现数据源的动态路由。
java
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}
5. 配置动态数据源 (DataSourceConfig)
将主从数据源注入到 DynamicDataSource 中,并设为 MyBatis 的主数据源。
java
@Configuration
public class DataSourceConfig {
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@Primary
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource());
targetDataSources.put("slave", slaveDataSource());
dynamicDataSource.setDefaultTargetDataSource(masterDataSource()); // 默认主库
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
}
6. 使用 AOP 实现自动切换
通过 AOP 拦截 Mapper 层方法,根据方法名自动切换数据源。
java
@Aspect
@Component
public class DataSourceAspect {
@Before("execution(* com.yourpackage.mapper..*.*(..))")
public void beforeMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
if (methodName.startsWith("select") || methodName.startsWith("get")
|| methodName.startsWith("find") || methodName.startsWith("query")) {
DataSourceContextHolder.setDataSourceType("slave");
} else {
DataSourceContextHolder.setDataSourceType("master");
}
}
@After("execution(* com.yourpackage.mapper..*.*(..))")
public void afterMethod() {
DataSourceContextHolder.clearDataSourceType();
}
}
7. (可选) 强制读主库
若某些场景(如刚写完立即查询)必须读主库,可自定义 @Master 注解,并在 AOP 中优先判断。
🚀 方案二:使用专业中间件(ShardingSphere-JDBC)
如果未来有分库分表需求,ShardingSphere-JDBC 是更专业的选择。它是一个轻量级 Java 框架,在应用层透明地提供读写分离等功能。
配置示例 (application.yml):
yaml
spring:
shardingsphere:
datasource:
names: master,slave
master:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://<写库IP>:3306/<数据库名>?useSSL=false
username: root
password: <写库密码>
slave:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://<读库IP>:3306/<数据库名>?useSSL=false
username: root
password: <读库密码>
rules:
readwrite-splitting:
data-sources:
myds:
type: Static
props:
write-data-source-name: master
read-data-source-names: slave
props:
sql-show: true
🏛️ 方案三:使用数据库中间件(如 MyCat, ProxySQL)
这种方式将读写分离逻辑从应用剥离,由独立的中间件服务负责 SQL 路由。应用只连接中间件,由中间件将写 SQL 转发到主库,读 SQL 负载均衡到从库。此方案对应用完全透明,但需要额外部署和维护中间件。
⚠️ 重要注意事项
- 主从同步延迟 :这是读写分离的经典问题。从库数据同步有延迟,可能导致刚写入的数据读不到。解决方案包括:1) 使用
@Master注解让关键读操作强制走主库;2) 对于非实时性要求高的查询,可以接受短暂延迟。 - 事务管理 :在
@Transactional标注的方法内,应全程使用主库,避免读写分离导致事务内数据不一致。可在事务拦截器中设置数据源为主库。 - 故障转移:生产环境需考虑从库宕机的情况,应有机制将读请求降级到主库。
- 数据源配置 :在方案一的
DataSourceConfig中,@Primary注解必须加在dynamicDataSource()上,确保 MyBatis 使用它作为默认数据源。
💎 总结与建议
- 快速入门/学习 :推荐方案一 (
AbstractRoutingDataSource+ AOP),能让你深入理解原理,实现也直接。 - 生产环境/复杂需求 :推荐方案二 (ShardingSphere-JDBC),它功能强大且对代码侵入小,能更好地应对未来扩展。
- 大规模/运维主导 :可考虑方案三 (独立中间件),实现应用与数据库的解耦。
另外要强调一下,springboot直接连mycat就可以了,
在mycat的配置文件里做配置,实现主从分离