经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
spring多数据源动态切换的实现原理及读写分离的应用
来源:cnblogs  作者:京东云技术团队  时间:2023/8/11 8:38:09  对本文有异议

简介

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中配置主从数据库连接信息
  1. spring:
  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

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

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

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

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

  1. public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
  2. /**
  3. * 获取目标数据源标识
  4. */
  5. @Override
  6. protected Object determineCurrentLookupKey() {
  7. return DataSourceHolder.getDbName();
  8. }
  9. }
  1. public 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. }

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

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

  1. @Configuration
  2. public 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. }

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

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

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

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

  1. @Aspect
  2. @Component
  3. @Slf4j
  4. public 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. }

作者:京东科技 郭艳红

来源:京东云开发者社区

原文链接:https://www.cnblogs.com/jingdongkeji/p/17616194.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号