您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
spring多数据源动态切换的实现原理及读写分离的应用
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
spring多数据源动态切换的实现原理及读写分离的应用
gy****
2023-08-02
IP归属:北京
6520浏览
## 简介 `AbstractRoutingDataSource`是Spring框架中的一个抽象类,可以实现多数据源的动态切换和路由,以满足复杂的业务需求和提高系统的性能、可扩展性、灵活性。 ## 应用场景 1. 多租户支持:对于多租户的应用,根据当前租户来选择其对应的数据源,实现租户级别的隔离和数据存储。 2. 分库分表:为了提高性能和扩展性,将数据分散到多个数据库或表中,根据分片规则来选择正确的数据源,实现分库分表。 3. 读写分离:为了提高数据库的读写性能,可能会采用读写分离的方式,根据读写操作的类型来选择合适的数据源,实现读写分离。 4. 数据源负载均衡:根据负载均衡策略来选择合适的数据源,将请求均匀地分配到不同的数据源上,提高系统的整体性能和可伸缩性。 5. 多数据库支持:在一些场景下,可能需要同时连接多个不同类型的数据库,如关系型数据库、NoSQL数据库等。根据业务需求选择不同类型的数据源,实现对多数据库的支持。 ## 实现原理 1. `AbstractRoutingDataSource`实现了`DataSource`接口,作为一个数据源的封装类,负责路由数据库请求到不同的目标数据源 ![AbstractRoutingDataSource hierarchy.png](https://s3.cn-north-1.jdcloud-oss.com/shendengbucket1/2023-07-28-16-11kZ4gEeS6ABApM86.png) 2. 该类中定义了一个`determineTargetDataSource`方法,会获取当前的目标数据源标识符,进而返回真正的数据源; 值得注意的是:其中`determineCurrentLookupKey` 为抽象方法,明显是要让用户自定义实现获取数据源标识的业务逻辑。 ![determineTargetDataSource.png](https://s3.cn-north-1.jdcloud-oss.com/shendengbucket1/2023-07-28-16-12pqaoELZIVoF0ERT.png) 3. 当系统执行数据库操作之前,会先获取数据源链接,即调用`getConnection`方法,该类重写的`getConnection`方法,会获取到真正的目标数据源,进而将数据库操作委托给目标数据源进行处理。 ![getConnection.png](https://s3.cn-north-1.jdcloud-oss.com/shendengbucket1/2023-07-28-16-12TjGAz6ItpgvVbRB.png) ## 读写分离实现V1版 1. yml中配置主从数据库连接信息 ```yml spring: datasource: business-master: url: jdbc:mysql://ip1:3306/xxx username: c_username password: p1 business-slaver: url: jdbc:mysql://ip2:3306/xxx username: c_username password: p2 ``` 2. 读取yml中的主从数据源配置 ```java @Data @ConfigurationProperties(prefix = "spring.datasource") @Component public class DataSourcePropertiesConfig { /** * 主库配置 */ DruidDataSource businessMaster; /** * 从库配置 */ DruidDataSource businessSlaver; } ``` 3. 自定义动态数据源类 `DynamicRoutingDataSource`,继承 `AbstractRoutingDataSource` 类,并重写 `determineCurrentLookupKey` 方法,定义获取目标数据源标识的逻辑。 此处的逻辑为:定义一个 `DataSourceHolder` 类,将数据源标识放到 `ThreadLocal` 中,当需要时从 `ThreadLocal` 中获取。 ```java public class DynamicRoutingDataSource extends AbstractRoutingDataSource { /** * 获取目标数据源标识 */ @Override protected Object determineCurrentLookupKey() { return DataSourceHolder.getDbName(); } } ``` ```java public class DataSourceHolder { /** * 当前线程使用的 数据源名称 */ private static final ThreadLocal<String> THREAD_LOCAL_DB_NAME = new ThreadLocal<>(); /** * 设置数据源名称 */ public static void setDbName(String dbName) { THREAD_LOCAL_DB_NAME.set(dbName); } /** * 获取数据源名称,为空的话默认切主库 */ public static String getDbName() { String dbName = THREAD_LOCAL_DB_NAME.get(); if (StringUtils.isBlank(dbName)) { dbName = DbNameConstant.MASTER; } return dbName; } /** * 清除当前数据源名称 */ public static void clearDb() { THREAD_LOCAL_DB_NAME.remove(); } } ``` 4. 创建动态数据源`DynamicRoutingDataSource`对象,并注入到容器中。这里创建了主从两个数据源,并进行了初始化,分别为其设置了数据源标识并放到了`DynamicRoutingDataSource`对象中,以便后面使用。 若为多个数据源,可参考此处进行批量定义。 ```java @Configuration public class DataSourceConfig { @Autowired private DataSourcePropertiesConfig dataSourcePropertiesConfig; /** * 主库数据源 */ public DataSource masterDataSource() throws SQLException { DruidDataSource businessDataSource = dataSourcePropertiesConfig.getBusinessMaster(); businessDataSource.init(); return businessDataSource; } /** * 从库数据源 */ public DataSource slaverDataSource() throws SQLException { DruidDataSource businessDataSource = dataSourcePropertiesConfig.getBusinessSlaver(); businessDataSource.init(); return businessDataSource; } /** * 动态数据源 */ @Bean public DynamicRoutingDataSource dynamicRoutingDataSource() throws SQLException { DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource(); Map<Object, Object> targetDataSources = new HashMap<>(2); targetDataSources.put("master", masterDataSource()); targetDataSources.put("slaver", slaverDataSource()); dynamicRoutingDataSource.setDefaultTargetDataSource(masterDS); dynamicRoutingDataSource.setTargetDataSources(targetDataSources); dynamicRoutingDataSource.afterPropertiesSet(); return dynamicRoutingDataSource; } } ``` 5. 自定义一个注解,指定数据库。 可以将一些常用的查询接口自动路由到读库,以减轻主库压力。 ```java @Documented @Inherited @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface DataSourceSwitch { /** * 数据源名称,默认主库 */ String dbName() default "master"; } ``` 6. 定义一个切面,拦截所有 `Controller` 接口,使用 `DataSourceSwitchRead` 注解的方法,将统一路由到读库查询 ```java @Aspect @Component @Slf4j public class DataSourceAspect { /** * 切库,若为多个从库,可在这里添加负载均衡策略 */ @Before(value = "execution ( * com.jd.gyh.controller.*.*(..))") public void changeDb(JoinPoint joinPoint) { Method m = ((MethodSignature) joinPoint.getSignature()).getMethod(); DataSourceSwitch dataSourceSwitch = m.getAnnotation(DataSourceSwitch.class); if (dataSourceSwitch == null) { DataSourceHolder.setDbName(DbNameConstant.MASTER); log.info("switch db dbName = master"); } else { String dbName = dataSourceSwitch.dbName(); log.info("switch db dbName = {}", dbName); DataSourceHolder.setDbName(dbName); } } } ```
上一篇:【王牌测评】轻量云主机能用来做什么?
下一篇:从原理聊JVM(四):JVM中的方法调用原理
gy****
文章数
4
阅读量
647
作者其他文章
01
Spring缓存是如何实现的?如何扩展使其支持过期删除功能?
前言:在我们的应用中,有一些数据是通过rpc获取的远端数据,该数据不会经常变化,允许客户端在本地缓存一定时间。该场景逻辑简单,缓存数据较小,不需要持久化,所以不希望引入其他第三方缓存工具加重应用负担,非常适合使用Spring Cache来实现。但有个问题是,我们希望将这些rpc结果数据缓存起来,并在一定时间后自动删除,以实现在一定时间后获取到最新数据。类似Redis的过期时间。接下来是我的调研步骤
01
spring多数据源动态切换的实现原理及读写分离的应用
简介AbstractRoutingDataSource是Spring框架中的一个抽象类,可以实现多数据源的动态切换和路由,以满足复杂的业务需求和提高系统的性能、可扩展性、灵活性。应用场景多租户支持:对于多租户的应用,根据当前租户来选择其对应的数据源,实现租户级别的隔离和数据存储。分库分表:为了提高性能和扩展性,将数据分散到多个数据库或表中,根据分片规则来选择正确的数据源,实现分库分表。读写分离:为
01
从源码层面深度剖析Spring循环依赖
以下举例皆针对单例模式讨论图解参考 https://www.processon.com/view/link/60e3b0ae0e3e74200e2478ce1、Spring 如何创建Bean?对于单例Bean来说,在Spring容器整个生命周期内,有且只有一个对象。Spring 在创建 Bean 过程中,使用到了三级缓存,即 DefaultSingletonBeanRegistry.java 中定
01
手把手教你如何扩展(破解)mybatisplus的sql生成
mybatisplus 的常用CRUD方法众所周知,mybatisplus提供了强大的代码生成能力,他默认生成的常用的CRUD方法(例如插入、更新、删除、查询等)的定义,能够帮助我们节省很多体力劳动。他的BaseMapper中定义了这些常用的CRUD方法,我们在使用时,继承这个BaseMapper类就默认拥有了这些能力。如果我们的业务中,需要类似的通用Sql时,该如何实现呢?是每个Mapper中都
gy****
文章数
4
阅读量
647
作者其他文章
01
Spring缓存是如何实现的?如何扩展使其支持过期删除功能?
01
从源码层面深度剖析Spring循环依赖
01
手把手教你如何扩展(破解)mybatisplus的sql生成
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号