在项目中引入ShardingJDBC并整合MybatisPlus
ShardingJDBC介绍
SharingdJDBC是ShardSphere中的一种实现方式(ShardingSphere-Proxy是代理,相当于一个中间件,把所有的请求统一发到Proxy上,在Proxy上对请求进行处理;最后一个是Sidecar,相当于提供一个大网,将所有的数据库织起来,一般用于微服务中)它一个轻量级的Java框架,集成在程序里,通过jar包的方式提供服务,并且支持很多ORM框架,比如我们经常使用的Mybatis、JPA、Hibernate等等
ShardingJDBC的一些概念
逻辑表:我们一般把结构和逻辑一致的表称为逻辑表,例如user表我拆分为了user01和user02,除了名字以外,其他都一样,那么user就是一张逻辑表。
真实表:真实表就是真实存在的表,上面的user01和user02就是真实表
广播表:公用的一些表,相当于抽象版的”工具类”
分片键:我们分表是按照哪个字段去分,这个字段就是分片键
分片算法:确定分片键后要如何去计算到具体的表。常用的有取模运算、
hash算法以及
范围算法`
分片策略:由分片键和分片算法
组成。用于确定数据应该存储在哪个分片中的算法或规则。例如行表达式分片策略、标准分片策略、范围分片策略…
分片规则管理器:负责管理分片规则和分片策略,根据分片规则将数据分配到不同的数据库节点中。
数据库路由器:根据SQL语句中的分片键,通过分片规则管理器将数据路由到相应的数据库节点中。
数据库代理:负责将SQL语句发送到相应的数据库节点,并将结果合并后返回给客户端。
项目实战
引入依赖
我们在项目中引入一个Sharding的依赖,这里我用的是5.2.1版本。本次我也整合了MybatisPlus,所以需要多引入一个MybatisPlus的依赖以及druid连接池
1 | <!--ShardingJDBC--> |
配置文件
ShardingJDBC最核心的就是配置部分,这里我使用单库双表实现水平分表先做个小demo,帮助大家快速了解认识Sharding。
我在数据库中有2张表,分别为sora_user01和sora_user02。打算每次插入数据的时候根据id取模判断应该落到01还是02。然后用户读取的时候从2张表中一起获取数据。
水平分表可以降低单表的数据量。demo后面会用2个数据源和2张表来还原项目的真实情况
1 | server: |
Sharding的配置我把它分为三大类,分别是逻辑表的配置
分表/分库策略id生成策略
这三个都要配置在 rules下的 sharding中
首先来说说分表/分库的策略配置,这部分对应的是配置文件中的sharding-algorithms,我们需要先配置算法名称,这个是自定义的,例如我们给user表配置分片算法,那么就可以叫user-inline。随后在算法中配置2个属性,分别是type类型,我们使用INLINE表达式。然后是props中的algorithm-expression配置分片算法的具体逻辑。我这里按照id%2+1,平摊在01和02两个表中。
再来说逻辑表的配置,对应配置文件中tables,我们首先配置表名,这个是自定义的名字,我习惯跟Java的实体类对应(我也试过使用过数据库的表名字发现有问题)。接着需要配置真实表,也就是actualDataNodes的内容,由数据源+表名组成,配置文件中的注释写的很清楚。下面是分片策略tableStrategy,这个是固定的。我们需要选择一个分片策略。ShardingJDBC一共有4种分片策略。
standard-适用于单分片键场景
complex-适用于多分片键场景
hint-通过Hint指定分片值进行分片
none-不分片
我们一般使用单分片键来进行分片,所以选择standard。配置2个属性,分别为分片列,也就是按照哪个字段进行分片。第二个属性为分片算法,我们引入刚刚自定义的user-inline算法就可以。
最后是主键的生成策略,一般分布式项目我们使用雪花id就可以了,固定使用SNOWFLAKE来完成。
如果没有看明白或者想了解更多的可以去看看官网的说明
后端代码
经典的MybatisPlus,这里就不过多赘述了
新增用户的代码:
1 | /** |
查询代码:
1 | /** |
测试结果
以上的工作完成后,就可以试验了,先使用postman添加了11条数据
可以发现id尾数为奇数的在02表中,id为偶数的在01表内。成功按照分片策略添加成功。接着我们试一下查询
发现报错了,说的是和分片算法不匹配。
我们看一下源码,发现id是String类型的,String类型是不可以直接使用%运算的。
看一下controller,将String改为Long即可
再试一次,查询成功
当然排查这个错误,我自己也排查了将近半天,各种论坛和github也提问了,期间也问了很多次ChatGPT也没结果,最后反正就是看到GPT在INLINE表达式中用了Math函数才突然有了灵感🤔
双数据源双表实战
我们接下来尝试在2个数据源中分库分表的策略。还是一样我们加一个分库的策略,叫database-inline,然后在tables表中引入database的分片策略。
1 | #shardingjdbc主要配置 |
根据上面的配置我们可以推断出来。id的尾数只有可能是偶数或奇数,偶数的话,分库算法模出来的就是0也就是ds0数据源,同理,分表算法模出来的是0+1也就是user01表,那么反之,奇数对应的是ds1数据源的user02表。我们还是先插入几条配置。
ds0的user01表的数据:
ds1的user02表的数据:
然后我们分别用01的id和02的id去查询都是可以查出来的,这里我就不放图了
实现原理
功能上实现后我们就可以分析一下实现流程了。
这里我先把官方的解释放出来
SQL 解析
分为词法解析和语法解析。 先通过词法解析器将 SQL 拆分为一个个不可再分的单词。再使用语法解析器对 SQL 进行理解,并最终提炼出解析上下文。 解析上下文包括表、选择项、排序项、分组项、聚合函数、分页信息、查询条件以及可能需要修改的占位符的标记。
SQL 路由
根据解析上下文匹配用户配置的分片策略,并生成路由路径。目前支持分片路由和广播路由。
SQL 改写
将 SQL 改写为在真实数据库中可以正确执行的语句。SQL 改写分为正确性改写和优化改写。
SQL 执行
通过多线程执行器异步执行。
结果归并
将多个执行结果集归并以便于通过统一的 JDBC 接口输出。结果归并包括流式归并、内存归并和使用装饰者模式的追加归并这几种方式。
查询优化
由 Federation 执行引擎(开发中)提供支持,对关联查询、子查询等复杂查询进行优化,同时支持跨多个数据库实例的分布式查询,内部使用关系代数优化查询计划,通过最优计划查询出结果。
经过我自己的整理我把它分为了4步
- 客户端发送SQL语句到ShardingJDBC。
- 数据库路由器解析SQL语句,提取分片键。
- 数据库路由器通过分片规则管理器获取数据应该存储在哪个数据库节点中。
- 数据库代理将SQL语句发送到相应的数据库节点,并将结果合并后返回给客户端。