SpringBoot实现MySQL读写分离
在⾼并发下,需要对应⽤进⾏读写分离,配置多数据源,即写操作⾛主库,读操作则⾛从库,主从数据库负责各⾃的读和写,缓解了锁的争⽤,提⾼了读取性能。
实现读写分离有多种⽅式,如使⽤中间件MyCat、Sharding-JDBC等,这⾥我们使⽤Aop的⽅式在代码层⾯实现读写分离。
实现原理
惊喜是什么意思实现读写分离,⾸先要对Mysql做主从复制,即搭建⼀个主数据库,以及⼀个或多个从数据库。
具体实现主从复制,可参照前⼀篇博客
使⽤Aop的⽅式,当调⽤业务层⽅法前,判断请求是否是只读操作,动态切换数据源,如果是只读操作,则切换从数据库的数据源,否则使⽤主数据库的数据源。
系统架构
项⽬仓库:
这是我之前写的⼀个项⽬,具体代码可以在可以在上⾯我的GitHub仓库中找到,项⽬就是使⽤了本⽂章介绍的读写分离⽅式,感兴趣的同学可以作为参考。
代码实现
在l配置MySQL
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
#主机
master:
urname: root
password:123456
url: jdbc:mysql://服务器ip:3306/letfit?uSSL=fal&uUnicode=true&characterEncoding=utf8&rverTimezone=GMT driver-class-name: sql.cj.jdbc.Driver
#从机
slave:
urname: root
password:123456
url: jdbc:mysql://服务器ip:3307/letfit?uSSL=fal&uUnicode=true&characterEncoding=utf8&rverTimezone=GMT driver-class-name: sql.cj.jdbc.Driver
#连接池
druid:
initialSize:5
minIdle:5
maxActive:20
maxWait:60000
timeBetweenEvictionRunsMillis:60000
minEvictableIdleTimeMillis:300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle:true
testOnBorrow:fal
testOnReturn:fal
poolPreparedStatements:true
filters: stat,wall
maxPoolPreparedStatementPerConnectionSize:20平翘舌音有哪些
uGlobalDataSourceStat:true关于想的词语
connectionProperties: Sql=true;druid.stat.slowSqlMillis=500
创建ReadOnly注解
在业务层的⽅法上使⽤该注解,使⽤ ReadOnly 注解的⽅法只处理读操作,会切换到从机的数据源package;
/**
* 只读注解
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public@interface ReadOnly {
}
创建枚举类
定义两个枚举类型 MASTER、slave分别代表数据库类型
package;
/**
* 数据库类型
*/
public enum DBTypeEnum {
/**
* 主数据库
*/
新中国万岁MASTER,
/**
* 从数据库
*/
SLAVE;
}
编写动态切换数据源的⼯具类
package;
/**
* 动态切换数据源⼯具类
*/
@Slf4j
public class DynamicDbUtil {
/
**
* ⽤来存储代表数据源的对象
*/
private static final ThreadLocal<DBTypeEnum> CONTEXT_HAND =new ThreadLocal<>();
/**
* 切换当前线程要使⽤的数据源
* @param dbTypeEnum
*/
public static void t(DBTypeEnum dbTypeEnum){
CONTEXT_HAND.t(dbTypeEnum);
log.info("切换数据源:{}", dbTypeEnum);
}
/**
* 切换到主数据库
*/
public static void master(){
t(DBTypeEnum.MASTER);
}
/**
* 切换到从数据库
*/
public static void slave(){
t(DBTypeEnum.SLAVE);
}
/**
* 移除当前线程使⽤的数据源
*/
public static void remove(){
ve();
}
/**
* 获取当前线程使⽤的枚举类
* @return
美女性感写真图片*/
竹石诗意public static DBTypeEnum get(){
return ();
}
}
劳安简历编写AbstractRoutingDataSource的实现类
Spring boot提供了AbstractRoutingDataSource 根据⽤户定义的规则选择当前的数据源,这样我们可以在执⾏查询之前,设置使⽤的数据源。实现可动态路由的数据源,在每次数据库查询操作前执⾏。它的抽象⽅法 determineCurrentLookupKey() 决定使⽤哪个数据源。
AbstractRoutingDataSource 的部分源码如下
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
/
*
* ⽤来存储多个数据源
*/
@Nullable
private Map<Object, Object> targetDataSources;
/*
* 默认数据源
*/
@Nullable
private Object defaultTargetDataSource;
@Nullable
private Map<Object, DataSource> resolvedDataSources;
@Nullable
private DataSource resolvedDefaultDataSource;
public AbstractRoutingDataSource(){
}
/*
* 设置多数据源,最终使⽤哪个数据源由determineTargetDataSource()返回决定
*/
public void tTargetDataSources(Map<Object, Object> targetDataSources){
this.targetDataSources = targetDataSources;
}
/
*
* 设置默认数据源
*/
public void tDefaultTargetDataSource(Object defaultTargetDataSource){
this.defaultTargetDataSource = defaultTargetDataSource;
}
/*
* 决定使⽤的数据源,选择的策略需要我们⾃⼰去定义
*/
protected DataSource determineTargetDataSource(){
/
/调⽤determineCurrentLookupKey()获取数据源的key
Object lookupKey =this.determineCurrentLookupKey();
//根据key获取对应数据源平菇做法
DataSource dataSource =((lookupKey);
if(dataSource ==null&&(this.lenientFallback || lookupKey ==null)){
dataSource =solvedDefaultDataSource;
}
if(dataSource ==null){
throw new IllegalStateException("Cannot determine target DataSource for lookup key ["+ lookupKey +"]");
}el{
return dataSource;
}
}
/*
* 抽象⽅法,需要我们⾃⼰去实现
*/
@Nullable
protected abstract Object determineCurrentLookupKey();
}
编写 DynamicDataSource继承 AbstractRoutingDataSource