业务背景
数据库访问压力大时,常用做法是使用 redis缓存,当数据库写的压力大,需要增加数据库节点,根据压力大小落地不同的方案,例如读写分离,一主多从,多主多从等.
Spring AbstractRoutingDataSource 类介绍
AbstractRoutingDataSource 是一个抽象类,它实现了 Spring Framework 的 DataSource 接口。它可以用来实现动态数据源的功能。 在传统的应用程序中,通常只会配置一个静态的数据源,用于与数据库进行交互。但是在一些特殊的场景下,可能需要根据不同的条件或者业务需求来动态地切换数据源,比如多租户系统、读写分离等。
AbstractRoutingDataSource 的实现思路是,通过一个 ThreadLocal 变量来保存当前线程所使用的数据源标识。当需要获取数据源时,AbstractRoutingDataSource 会根据当前线程的数据源标识选择相应的数据源,并将其返回
AbstractRoutingDataSourced 的 diagram
实现步骤
- 在配置文件中定义各个数据源的 Bean,可以是不同的 DataSource 对象,也可以是不同的 DriverManagerDataSource 对象
package com.github.dynamic.datasource.configuration;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map;
public class CustomDynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> CONTEXT_HOLDER =ThreadLocal.withInitial(String::new);
public CustomDynamicDataSource(DataSource defaultDataSource, Map<Object, Object> targetDatasource){
super.setDefaultTargetDataSource(defaultDataSource);
super.setTargetDataSources(targetDatasource);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
return getDataSource();
}
public static void setDataSource(String dataSourceName) {
CONTEXT_HOLDER.set(dataSourceName);
}
public static String getDataSource() {
return CONTEXT_HOLDER.get();
}
public static void clearDataSource() {
CONTEXT_HOLDER.remove();
}
}
- 在配置文件中配置 AbstractRoutingDataSource Bean,并将各个数据源的 Bean 注入到 AbstractRoutingDataSource 中
package com.github.dynamic.datasource.configuration;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.github.dynamic.datasource.annotation.DataSourceAspectProcessor;
import com.github.dynamic.datasource.constant.DataSourceNameConstant;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DynamicDataSourceConfigAutoConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource(){
return DruidDataSourceBuilder
.create()
.build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource(){
return DruidDataSourceBuilder
.create()
.build();
}
@Bean
@Primary
public CustomDynamicDataSource dynamicRoutingDatasource(DataSource masterDataSource, DataSource slaveDataSource){
Map<Object, Object> targetDatasource = new HashMap<>(2);
targetDatasource.put(DataSourceNameConstant.MASTER, masterDataSource);
targetDatasource.put(DataSourceNameConstant.SLAVE, slaveDataSource);
return new CustomDynamicDataSource(masterDataSource, targetDatasource);
}
@Bean
public DataSourceAspectProcessor dataSourceAspectProcessor(){
return new DataSourceAspectProcessor();
}
}
- 基于Aop代理去做数据源动态切换
package com.github.dynamic.datasource.annotation;
import com.github.dynamic.datasource.configuration.CustomDynamicDataSource;
import com.github.dynamic.datasource.constant.DataSourceNameConstant;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import java.lang.reflect.Method;
@Slf4j
@Aspect
public class DataSourceAspectProcessor implements AutoCloseable, Ordered {
@Pointcut("@annotation(DSRouter)")
public void pointcut(){
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DSRouter router = method.getAnnotation(DSRouter.class);
if (router == null) {
CustomDynamicDataSource.setDataSource(DataSourceNameConstant.MASTER);
log.debug("set datasource is " + DataSourceNameConstant.MASTER);
} else {
CustomDynamicDataSource.setDataSource(router.value());
log.debug("set datasource is " + router.value());
}
try {
return point.proceed();
} finally {
close();
log.debug("clean datasource");
}
}
@Override
public int getOrder() {
return 1;
}
@Override
public void close() throws Exception {
CustomDynamicDataSource.clearDataSource();
}
}
这是比较简单的做法,也比较实用. 完整代码放在了 gitee上面 gitee.com/zerosj/dyna…