自己动手写一个分库分表中间件(三)数据源路由实现
相关文章:
- 自己动手写一个分库分表中间件(一)思考
- 自己动手写一个分库分表中间件(二)数据源定义和分片代理层设计
- 排查项目中读写分离失效原因
- 小议 Java 内省机制
注:本文内容暂不涉及事务相关的问题及配置,事务问题会在后面的文章中重点介绍。
本文主要介绍分库分表最重要的功能,数据路由的实现。
1. 配置解析
数据路由第一步是要解析配置,根据两篇文章的介绍,从物理上,或者说 DataSource
的级别,我们是“business+model”构成一个数据源,但是还有分表的配置,所以逻辑上的数据源是由“business+model+表配置”构成。
配置的解析实现很简单,无非就是解析一下 properties 或者 yml 文件。
这里有个小细节就是如何优雅的把配置文件装配成一个 Java 的对象或者说 Spring 的 Bean。这里可以使用 @ConfigurationProperties
或者基于 Java 内省机制去做。
2. 装配数据源
与 Sharding-JDBC 扩展 JDBC 功能不同,我们还是要强依赖 Spring 的,所以这里动态数据源需要扩展 Spring 的 AbstractRoutingDataSource
。Spring 动态数据源的设计是一个标准的模版设计模式。里面有一个 Map<Object, Object>
类型的 targetDataSources
变量,这里的 value 就是 DataSource
,key 就是我们定义的 DataSource
级别的数据源标识,我这里就是 business+model。
我们还需要实现AbstractRoutingDataSource
的抽象方法 determineCurrentLookupKey()
,从方法名不难看出,这就是告诉 Spring 现在的路由标识 key 是哪个 ,然后 Spring 就会从 targetDataSources
这个 Map
里面取出 DataSource
进而获取 Connection
。
大致代码如下:
/*** @author Dongguabai* @description* @date 2022-04-10 21:13*/
public class DynamicDataSource extends AbstractRoutingDataSource {//通过 ThreadLocal 传递数据源路由 keypublic static final ThreadLocal<String> KEY_HOLDER = new ThreadLocal<>();@Overrideprotected Object determineCurrentLookupKey() {return KEY_HOLDER.get();}
}
@Bean@Primarypublic DynamicDataSource dataSource(DataSource first, DataSource second) {Map<Object, Object> targetDataSources = new HashMap<>(5);targetDataSources.put("FIRST", first);targetDataSources.put("SECOND", second);DynamicDataSource dynamicDataSource = new DynamicDataSource();dynamicDataSource.setTargetDataSources(targetDataSources);return dynamicDataSource;}
还有一个细节是这里基于 ThreadLocal
进行数据源 key 的传递,相对来说更方便和隐式。
因为我们系统使用的是 MyBatis,所以这里也需要跟 SqlSessionFactor
进行绑定:
@Beanpublic SqlSessionFactory shardingSqlSessionFactory(DataSource dataSource) throws Exception {SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();sessionFactory.setDataSource(dataSource);return sessionFactory.getObject();}
到这里,动态数据源就已经配置好了。
还有一个问题是我们需要去指定 key,也就是说需要在适当的时机将 key 设置到 DynamicDataSource
的 KEY_HOLDER
中去。
3. 动态数据源切面
很明显需要使用到 Spring AOP。其实上面第 2 步数据源 Map
我们都有了,第 3 步如何去指定数据源路由 key 完全就可以自由发挥了。
这里说一下我个人的设计思路,动态数据源切面里要解决三个问题:
- 开发同学如何设置
key
(开发同学如何使用) - 切哪里
KEY_HOLDER
里面到值什么时候remove
掉
3.1 设置数据源标识
这里提供了两种编程模型:
- 注解编程
- API 编程
可以提供一个注解:
/*** @author Dongguabai* @description* @date 2022-04-10 21:36*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DynamicRoute {String business() default "";String model() default "slave";
}
DynamicRoute
是标注在方法上,表示这个方法内部的数据库操作路由。那么上面说的 3 个问题都好解决了,直接切被 DynamicRoute
标注的方法,方法执行前解析注解里面的属性,设置到 KEY_HOLDER
中去,然后 DynamicDataSource
就会从 KEY_HOLDER
中拿到数据源 key,获取对应的 Connection
。
目标方法执行完成后 finally
执行 KEY_HOLDER
的 remove()
即可。
但是这个注解有个问题,太“静态”了,可能更适合读写分离的场景,比如我就希望这个方法查询主库或者报表库,加注解指定一下 model 就行了。但是更多的时候我们会对 SQL 参数去动态的解析,进而确定这个 SQL 要走哪个库。
这种场景就可以再提供一个注解:
/*** @author Dongguabai* @description* @date 2022-03-23 02:14*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface DynamicRoutingKey {RoutingKeyType type() default RoutingKeyType.ORDER_ID;String model() default "slave";
}
这个注解是标注在方法参数上的,比如标注的是订单 Id。解析出标注的订单 Id 后根据我们自己的规则找出 business 和 model,进而确定数据源 key。
再回看上面的三个问题,可以切自己动手写一个分库分表中间件(二)数据源定义和分片代理层设计中提到的代理层中的方法,如果有参数被 DynamicRoutingKey
标注那么就解析设置数据源路由 key 到 KEY_HOLDER
中去。
但有时候使用注解不太方便,所以我们还需要提供一种 API 的方式,可以让使用者更灵活的去设置数据源路由:
/*** @author Dongguabai* @description* @date 2022-04-10 21:37*/
public class ShardingUtils {public static void setIfAbsent(String business, String model) {KEY_HOLDER.set(business+"-"+model);}
}
以上就是三种给开发同学使用的标识数据源路由的方式。
其实实现方式和原理都不难,但是里面还是有一些值得注意的点,这里简单介绍一下。
3.2 实现过程中的注意事项
优先级问题
上面提供了三种方式都可以设置路由,由于无法完全限制开发同学的使用,所以万一有个代理层方法标注了
DynamicRoute
,方法参数也标注了DynamicRoutingKey
,同时方法内部也是用了 API 的方式去设置路由,那么到底以哪个为准呢,其实这是一个设计问题,主要符合我们的目的就行。这块我在设计上主要基于两个原则:
实现更方便
更容易理解
依据这两个原则我的设计是“外层优先”,只要外层指定了路由,那么内层的路由策略均失效,方法上>方法参数上>方法内部。
API 方式的作用范围
注解我们可以拿到切点,也就是
Method
,但是 API 的方式我们是拿不到的,所以 API 设置的路由 key 在哪里被 remove 掉呢。我这里的设计思路和PageHelper
是一样的,即只有下一个 SQL 才会生效。重入问题
第 1 点解决优先级的问题,但是还有一个问题,三种方式最终都是设置数据到
DynamicDataSource
的KEY_HOLDER
中去,但是切面是不一样的,那么就可能出现内部的切面把KEY_HOLDER
中的数据删除了,导致外层(外层优先)路由设置失效。这里可以在设置KEY_HOLDER
的同时设置一个标识(或者计数),然后在finally
执行remove
的时候根据标识判断是否要删除。
总结
本文主要介绍了基于 Spring 的 AbstractRoutingDataSource
实现动态数据源,即分库分表数据源来实现数据源的动态路由 。正如在第一篇文章自己动手写一个分库分表中间件(一)思考中说的,基于现有的框架的能力,其实分库分表写起来真的不难(当然也可以去扩展 Java JDBC,那样实现的话,整个框架的适用性就更强了,只要是符合 JDBC 规范的 ORM 框架都能去整合,但同样的,实现也会更复杂),更多地是考虑一些细节和使用的便捷性。
欢迎关注公众号:
自己动手写一个分库分表中间件(三)数据源路由实现相关推荐
- 分享下去年底写的分库分表中间件heisenberg
好久没有写博了,去年年底的时候写了一个分库分表中间件服务器,当时正在看绝命毒师,觉得heisenberg这个名字很叼,然后就以这个命名了,炼毒也要精益求精啊... 公司在java分布式这块的基础设施很 ...
- 去年底写的mysql分库分表中间件heisenberg
好久没有写博了,去年年底的时候写了一个分库分表中间件服务器,当时正在看绝命毒师,觉得heisenberg这个名字很叼,然后就以这个命名了,炼毒也要精益求精啊... 公司在java分布式这块的基础设施很 ...
- 【数据库与事务系列】分库分表中间件
前面讲了利用mybatis插件进行多数据源切换和分表的方案,但是对业务侵入性较强,当然给予mybatis-plus的对业务侵入性还好,但是支持的策略有限.场景有限. 所以业界诞生了很多分库分表中间件来 ...
- 一文快速入门分库分表中间件 Sharding-JDBC (必修课)
书接上文 <一文快速入门分库分表(必修课)>,这篇拖了好长的时间,本来计划在一周前就该写完的,结果家庭内部突然人事调整,领导层进行权利交接,随之宣布我正式当爹,紧接着家庭地位滑落至第三名, ...
- 一文快速入门分库分表中间件 Sharding-JDBC
一.Sharding-JDBC 简介 Sharding-JDBC 最早是当当网内部使用的一款分库分表框架,到2017年的时候才开始对外开源,这几年在大量社区贡献者的不断迭代下,功能也逐渐完善,现已更名 ...
- 【Sharding-JDBC系列二】一文快速入门分库分表中间件 Sharding-JDBC (必修课)
作为Sharding-JDBC 分库分表实战系列的开篇文章,我们在前文中回顾了一下分库分表的基础知识,对分库分表的拆分方式有了一定的了解,下边我们介绍一下 Sharding-JDBC框架和快速的搭建一 ...
- 关系型数据库分库分表中间件之选型
写在前面 本文主要介绍关系型数据库分库分表的中间件,主要包含中间件介绍.选项及其对比.虽然市面上很多分库分表中间件,但是大多数都是不友好或者社区活跃度不高的项目,当然还是有很多淘汰的中间件.目前,在实 ...
- 数据库分库分表中间件对比(很全)
数据库(分库分表)中间件对比 分区:对业务透明,分区只不过把存放数据的文件分成了许多小块,例如mysql中的一张表对应三个文件.MYD,MYI,frm. 根据一定的规则把数据文件(MYD)和索引文件( ...
- 支付宝分库分表中间件--zdal简介
中间件, 如果仅仅作为一名用户的话, 主要关注一下如何使用即可, 大多数情况下也就是配置. 下面简单的介绍一下支付宝的分库分表中间件--->zdal在web项目中的配置. 1, 在网上查阅相关资 ...
最新文章
- 2019年不可错过的45个AI开源工具,你想要的都在这里
- Eclipse 创建 Java 包
- 使用JWT实现单点登录(完全跨域方案)
- php软件开发--公众平台
- 本地虚拟机中匿名ftp上传文件失败的问题
- 给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1](简单易懂)
- php查询mysql增加模板消息_php 实现发送微信模板消息
- linux命令行显卡驱动,Linux下NVIDIA显卡驱动安装方法
- Chapter 1 贝叶斯推断的思想
- [原创]网站文章页面添加分享按钮,百度按钮代码复制使用时不显示问题解决!
- 定级阿里P7,300道Java面试题帮你全副武装
- python爬虫_网易音乐歌单
- 解读大学里的软件工程专业以及其他专业,给高考完的学弟妹一点参考
- 小米电视安装 Plex 打造家庭影院
- Nginx反向代理到另外一台服务器,域名解析IP变更后连接超时
- windows 程序设计 第一章
- 【初级C语言】表达式和基本语句(布尔型与0比较,浮点型与0比较,switch语句,提高循环语句的效率)
- WGCNA构建基因共表达网络详细教程
- 优酷路由宝增加php,优酷路由宝旗舰版YK-L2刷改华硕[N14U N54U]5G 2G的7620老毛子Padavan固件方法...
- 6大核心议题首度揭晓,2021下半年最强热点来了! | 2021世界区块链大会·杭州...