spring多数据源动态切换的实现原理及读写分离的应用 | 京东云技术团队

简介

AbstractRoutingDataSource是Spring框架中的一个抽象类,可以实现多数据源的动态切换和路由,以满足复杂的业务需求和提高系统的性能、可扩展性、灵活性。

应用场景

  1. 多租户支持:对于多租户的应用,根据当前租户来选择其对应的数据源,实现租户级别的隔离和数据存储。
  2. 分库分表:为了提高性能和扩展性,将数据分散到多个数据库或表中,根据分片规则来选择正确的数据源,实现分库分表。
  3. 读写分离:为了提高数据库的读写性能,可能会采用读写分离的方式,根据读写操作的类型来选择合适的数据源,实现读写分离。
  4. 数据源负载均衡:根据负载均衡策略来选择合适的数据源,将请求均匀地分配到不同的数据源上,提高系统的整体性能和可伸缩性。
  5. 多数据库支持:在一些场景下,可能需要同时连接多个不同类型的数据库,如关系型数据库、NoSQL数据库等。根据业务需求选择不同类型的数据源,实现对多数据库的支持。

实现原理

1.AbstractRoutingDataSource实现了DataSource接口,作为一个数据源的封装类,负责路由数据库请求到不同的目标数据源

AbstractRoutingDataSource hierarchy.png

2.该类中定义了一个determineTargetDataSource方法,会获取当前的目标数据源标识符,进而返回真正的数据源;

值得注意的是:其中determineCurrentLookupKey为抽象方法,明显是要让用户自定义实现获取数据源标识的业务逻辑。

determineTargetDataSource.png

3.当系统执行数据库操作之前,会先获取数据源链接,即调用getConnection方法,该类重写的getConnection方法,会获取到真正的目标数据源,进而将数据库操作委托给目标数据源进行处理。

getConnection.png

读写分离实现V1版

  1. yml中配置主从数据库连接信息
1spring: 2 datasource: 3 business-master: 4 url: jdbc:mysql://ip1:3306/xxx 5 username: c_username 6 password: p1 7 business-slaver: 8 url: jdbc:mysql://ip2:3306/xxx 9 username: c_username 10 password: p2 11 12

2.读取yml中的主从数据源配置

1@Data 2@ConfigurationProperties(prefix = "spring.datasource") 3@Component 4public class DataSourcePropertiesConfig { 5 /** 6 * 主库配置 7 */ 8 DruidDataSource businessMaster; 9 /** 10 * 从库配置 11 */ 12 DruidDataSource businessSlaver; 13} 14 15

3.自定义动态数据源类DynamicRoutingDataSource,继承AbstractRoutingDataSource类,并重写determineCurrentLookupKey方法,定义获取目标数据源标识的逻辑。

此处的逻辑为:定义一个DataSourceHolder类,将数据源标识放到ThreadLocal中,当需要时从ThreadLocal中获取。

1public class DynamicRoutingDataSource extends AbstractRoutingDataSource { 2 /** 3 * 获取目标数据源标识 4 */ 5 @Override 6 protected Object determineCurrentLookupKey() { 7 return DataSourceHolder.getDbName(); 8 } 9} 10 11
1public class DataSourceHolder { 2 /** 3 * 当前线程使用的 数据源名称 4 */ 5 private static final ThreadLocal<String> THREAD_LOCAL_DB_NAME = new ThreadLocal<>(); 6 /** 7 * 设置数据源名称 8 */ 9 public static void setDbName(String dbName) { 10 THREAD_LOCAL_DB_NAME.set(dbName); 11 } 12 /** 13 * 获取数据源名称,为空的话默认切主库 14 */ 15 public static String getDbName() { 16 String dbName = THREAD_LOCAL_DB_NAME.get(); 17 if (StringUtils.isBlank(dbName)) { 18 dbName = DbNameConstant.MASTER; 19 } 20 return dbName; 21 } 22 /** 23 * 清除当前数据源名称 24 */ 25 public static void clearDb() { 26 THREAD_LOCAL_DB_NAME.remove(); 27 } 28} 29 30

4.创建动态数据源DynamicRoutingDataSource对象,并注入到容器中。这里创建了主从两个数据源,并进行了初始化,分别为其设置了数据源标识并放到了DynamicRoutingDataSource对象中,以便后面使用。

若为多个数据源,可参考此处进行批量定义。

1@Configuration 2public class DataSourceConfig { 3 @Autowired 4 private DataSourcePropertiesConfig dataSourcePropertiesConfig; 5 /** 6 * 主库数据源 7 */ 8 public DataSource masterDataSource() throws SQLException { 9 DruidDataSource businessDataSource = dataSourcePropertiesConfig.getBusinessMaster(); 10 businessDataSource.init(); 11 return businessDataSource; 12 } 13 /** 14 * 从库数据源 15 */ 16 public DataSource slaverDataSource() throws SQLException { 17 DruidDataSource businessDataSource = dataSourcePropertiesConfig.getBusinessSlaver(); 18 businessDataSource.init(); 19 return businessDataSource; 20 } 21 /** 22 * 动态数据源 23 */ 24 @Bean 25 public DynamicRoutingDataSource dynamicRoutingDataSource() throws SQLException { 26 DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource(); 27 Map<Object, Object> targetDataSources = new HashMap<>(2); 28 targetDataSources.put("master", masterDataSource()); 29 targetDataSources.put("slaver", slaverDataSource()); 30 dynamicRoutingDataSource.setDefaultTargetDataSource(masterDS); 31 dynamicRoutingDataSource.setTargetDataSources(targetDataSources); 32 dynamicRoutingDataSource.afterPropertiesSet(); 33 return dynamicRoutingDataSource; 34 } 35} 36 37

5.自定义一个注解,指定数据库。

可以将一些常用的查询接口自动路由到读库,以减轻主库压力。

1@Documented 2@Inherited 3@Target({ElementType.METHOD}) 4@Retention(RetentionPolicy.RUNTIME) 5public @interface DataSourceSwitch { 6 /** 7 * 数据源名称,默认主库 8 */ 9 String dbName() default "master"; 10} 11 12

6.定义一个切面,拦截所有Controller接口,使用DataSourceSwitchRead注解的方法,将统一路由到读库查询

1@Aspect 2@Component 3@Slf4j 4public class DataSourceAspect { 5 /** 6 * 切库,若为多个从库,可在这里添加负载均衡策略 7 */ 8 @Before(value = "execution ( * com.jd.gyh.controller.*.*(..))") 9 public void changeDb(JoinPoint joinPoint) { 10 Method m = ((MethodSignature) joinPoint.getSignature()).getMethod(); 11 DataSourceSwitch dataSourceSwitch = m.getAnnotation(DataSourceSwitch.class); 12 if (dataSourceSwitch == null) { 13 DataSourceHolder.setDbName(DbNameConstant.MASTER); 14 log.info("switch db dbName = master"); 15 } else { 16 String dbName = dataSourceSwitch.dbName(); 17 log.info("switch db dbName = {}", dbName); 18 DataSourceHolder.setDbName(dbName); 19 } 20 } 21} 22

作者:京东科技 郭艳红

来源:京东云开发者社区

点赞
收藏

评论区

加载中...

相关推荐

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

我已经把它摸的透透的了!!!Spring 动态数据源设计实践,全面解析

Spring动态数据源动态数据源是什么?它能解决什么???在实际的开发中,同一个项目中使用多个数据源是很常见的场景。比如,一个读写分离的项目存在主数据源与读数据源。所谓动态数据源,就是通过Spring的一些配置来自动控制某段数据操作逻辑是走哪一个数据源。举个读写分离的例子,项目中引用了两个数据源,master、slave。通过Spring配置或扩展能力来

mysql中like用法

like的通配符有两种%(百分号):代表零个、一个或者多个字符。\(下划线):代表一个数字或者字符。1\.name以"李"开头wherenamelike'李%'2\.name中包含"云",“云”可以在任何位置wherenamelike'%云%'3\.第二个和第三个字符是0的值wheresalarylike'\00%'4\

Spring Boot 集成 Mybatis 实现双数据源

这里用到了SpringBootMybatisDynamicDataSource配置动态双数据源,可以动态切换数据源实现数据库的读写分离。添加依赖加入Mybatis启动器,这里添加了Druid连接池、Oracle数据库驱动为例。<dependency<groupIdorg.mybatis.spring

SpringBoot配置多数据源

SpringBoot配置多数据源核心技术点​在Spring2.x中引入了AbstractRoutingDataSource,该类充当了DataSource的路由中介,能有在运行时,根据某种key值来动态切换到真正的DataSource上。​Spring动态配置多数