当前位置: 首页 > 科技观察

实用:Spring的多租户数据源管理AbstractRoutingDataSource!

时间:2023-03-15 18:45:44 科技观察

本文转载自微信公众号《小姐姐的味道》,作者小姐姐养的02号狗。转载本文请联系味觉小姐公众号。在很多情况下,我们确实需要在一个服务中访问多个数据源。虽然它使整体设计不那么优雅,但现实世界确实需要它。例如,您的企业服务于两个比较大的客户,但您希望他们能够共享一组代码。也就是说,你的代码一开始没有考虑多租户的设计,后来却出现了这么蛋疼的需求。但值得庆幸的是,这不是爆炸性的租户增长。除了引入一些分库分表组件外,Spring本身还提供了AbstractRoutingDataSource的方法,使得管理大部分数据源成为可能。其实分库分表组件的使用有很多限制。你得先理清这狗屎山,然后你还要忍受中间件对你SQL的苛刻要求;相反,一些疯狂的方式可以使代码尽可能多地更改。减少。心不如行动。接下来,让我们看看它的具体实现。一、基本原理多个数据源之间动态切换的核心是spring底层提供了AbstractRoutingDataSource类,用于数据源路由。AbstractRoutingDataSource实现了DataSource接口,所以我们可以直接将其注入到DataSource的属性中。我们主要是继承这个类,在里面实现determineCurrentLookupKey()方法,这个方法只需要返回一个数据库的名字即可。比如Controller通过获取前端业务传过来的值来分发业务逻辑。它可以手动设置当前请求的数据库ID,然后路由到正确的数据库表。@ControllerpublicclassARDTestController{@GetMapping("test")publicvoidchifeng(){//db-a应该是上层传下来的属性,我们可以放在ThreadLocalDataSourceContextHolder.setDbKey("db-a");}}那么当sql语句执行时,它是如何知道需要切换到哪个数据源的呢?是不是需要一直传db-a属性?在Java中,可以使用ThreadLocal来绑定这个透明属性。Spring的嵌套事务等实现原理也是基于ThreadLocal的。因此,DataSourceContextHolder。本质上是一个操作ThreadLocal的类。publicclassDataSourceContextHolder{privatestaticInheritableThreadLocaldbKey=newInheritableThreadLocal<>();publicstaticvoidsetDbKey(Stringkey){dbKey.set(key);}publicstaticStringgetDbKey(){returndbKey.get();}}2.配置代码首先我们自定义配置文件的格式。如下代码所示,配置了db-a和db-b两个数据库。multi:dbs:db-a:driver-class-name:org.h2.Driverurl:jdbc:h2:mem:dba;MODE=MYSQL;DATABASE_TO_UPPER=false;db-b:driver-class-name:org.h2。Driverurl:jdbc:h2:mem:dbb;MODE=MYSQL;DATABASE_TO_UPPER=false;然后,我们将其解析为属性。@ConfigurationProperties(prefix="multi")@ConfigurationpublicclassDbsProperties{privateMap>dbs=newHashMap<>();publicMap>getDbs(){returndbs;}publicvoidsetDbs(Map>dbs){this.dbs=dbs;}}下一步是为整个应用程序配置默认数据源。可以看到,它的主要逻辑是在运行时从ThreadLocal中取回预先设定的值。publicclassDynamicDataSourceextendsAbstractRoutingDataSource{@OverrideprotectedObjectdetermineCurrentLookupKey(){returnDataSourceContextHolder.getDbKey();}}最后一步是在整个项目中设置默认的DataSource。注意,我们生成DynamicDataSource后,还需要提供targetDataSource和defaultTargetDataSource的值,它才能正常工作。@ConfigurationpublicclassDynamicDataSourceConfiguration{@AutowiredDbsPropertiesproperties;@BeanpublicDataSourcedataSource(){DynamicDataSourcedataSource=newDynamicDataSource();finalMaptargetDataSource=getTargetDataSource();dataSource.setTargetDataSources(targetDataSource);//TODO默认数据库需要设置dataSource.setDefaultDataTargetDataSource(().iterator().next();returndataSource;}privateMapgetTargetDataSource(){MapdataSources=newHashMap<>();this.properties.getDbs().entrySet().stream().forEach(e->{DriverManagerDataSourcedmd=newDriverManagerDataSource();dmd.setUrl(e.getValue().get("url"));dmd.setDriverClassName(e.getValue().get("driver-class-name"));dataSources.put(e.getKey(),dmd);});returndataSources;}}3.问题通过上面简单的代码,可以实现Spring简单的多数据源管理。但显然,它仍然存在很多问题。业务切换需要产品设计选择模式。前端可以将属性放在localStroage中保存,每次都可以使用拦截器传递变量。后端每次请求时,都需要带上目标db,可以放在ThreadLocal中。但是ThreadLocal存在线程透传的问题。如果任务中启用了子线程,则无法共享变量。由于表是动态选择的,JPA自动创建和更新等方式将不可用。不方便测试和单元测试。在测试接口的时候,也需要每次都指定指向的库。由于是修改数据源的模式,每次添加库都需要重启才能上线。如果要实现动态性,数据源破坏是一个问题。End对于一个微服务,默认的限制策略有很多。例如,不同域之间的服务不能共享一个数据库。这些基本原则,让微服务变得清晰,耳目一新,是一些基本原则。同理,如果我们在设计之初就把租户的字段ID添加到每个表中,那么写代码就会顺畅很多。但是世界上没有那么多的如果。为什么原则存在?当然,它们是供人们打破的。编程只是一种工具。反正代码在自己手里。怎么玩取决于你的需要和心情。条条大路通罗马,曲径通幽,风光无限。作者简介:品味小姐姐(xjjdog),一个不允许程序员走弯路的公众号。专注于基础架构和Linux。十年架构,每天百亿流量,与你探讨高并发世界,给你不一样的滋味。