SpringBoot中使用Sharding-JDBC实战(实战+版本兼容+Bug解决)
一、实战
1、引入 ShardingSphere-JDBC 的依赖
https://mvnrepository.com/artifact/org.apache.shardingsphere/shardingsphere-jdbc/5.5.0
<!-- https://mvnrepository.com/artifact/org.apache.shardingsphere/shardingsphere-jdbc -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc</artifactId>
<version>5.5.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-test-util</artifactId>
</exclusion>
</exclusions>
</dependency>
下面是我项目的一些版本信息:
SpringBoot 3.2.4
shardingsphere 5.5.0
2、修改yaml配置文件
application.yaml:
spring:
profiles:
active: dev
main:
allow-circular-references: true
datasource:
# ShardingSphere 对 Driver 自定义,实现分库分表等隐藏逻辑
driver-class-name: org.apache.shardingsphere.driver.ShardingSphereDriver
# ShardingSphere 配置文件路径
url: jdbc:shardingsphere:classpath:shardingsphere-config.yaml
# driver-class-name: ${quick.datasource.driver-class-name}
# url: jdbc:mysql://${quick.datasource.host}:${quick.datasource.port}/${quick.datasource.database}?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
# username: ${quick.datasource.username}
# password: ${quick.datasource.password}
# hikari:
# maximum-pool-size: 50
shardingsphere-config.yaml
名字必须相同,前面的 jdbc:shardingsphere:classpath: 是固定写法。
大致的配置结构如上。
dataSources:
ds_0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://127.0.0.1:3306/quick_pickup?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
username: root
password: 123456
# 规则配置
rules:
- !SINGLE
tables:
# 加载全部单表
- "ds_0.*"
- !SHARDING
tables:
integral_package_order:
actualDataNodes: ds_0.integral_package_order_${0..15} # 配置数据表分片规则
tableStrategy:
standard:
shardingColumn: user_id # 使用 user_id 作为分片键
shardingAlgorithmName: integral_package_order_table_hash_mod # 使用自定义的分片算法
shardingAlgorithms:
integral_package_order_table_hash_mod:
# type: HASH_MOD # 使用 HASH_MOD 算法
# props:
# sharding-count: 16 # 设置分片数量为 16
type: INLINE
props:
algorithm-expression: integral_package_order_${user_id % 16}
props:
sql-show: true # 控制台打印 SQL 日志,便于调试
# 单机模式 也有集群模式 此处本机选择单机即可 这个必须添加! 不然要报错
mode:
type: Standalone
其中注意必须加上:
就是两点可能错误:
1.xxx表无法加载
Caused by: org.apache.ibatis.executor.ExecutorException: Error preparing statement. Cause: org.apache.shardingsphere.infra.exception.kernel.metadata.TableNotFoundException: Table or view 'store' does not exist. at org.apache.ibatis.executor.statement.BaseStatementHandler.prepare(BaseStatementHandler.java:99) ~[mybatis-3.5.14.jar:3.5.14] at org.apache.ibatis.executor.statement.RoutingStatementHandler.prepare(RoutingStatementHandler.java:60) ~[mybatis-3.5.14.jar:3.5.14]
2.不能使用自动的算法这类型的报错
Caused by: com.zaxxer.hikari.pool.HikariPool$PoolInitializationException: Failed to initialize pool: Sharding algorithm
HASH_MOD
initialization failed, reason is:integral_package_order
tables sharding configuration can not use auto sharding algorithm.. at com.zaxxer.hikari.pool.HikariPool.throwPoolInitializationException(HikariPool.java:596) ~[HikariCP-5.0.1.jar:na]
上面截图中HashMod的分片数量里面这个integral_package_order_${order_id % 16}属性应该是16才对,但是还是报错,有大佬有解决可以评论区教教。
设置好需要分片的表,下面就是对表进行水平分片
3、对表进行水平分片
这里我写了一个测试案例可以帮忙我生成16个建表语句:
package com.quick.sharding;
public class IntegralPackageOrderShardingTest {
// 基础 SQL 模板,使用占位符 `%d` 替换分表的后缀
public static final String SQL_TEMPLATE = "create table integral_package_order_%d\n" +
"(\n" +
" id bigint not null comment '主键'\n" +
" primary key,\n" +
" user_id bigint unsigned not null comment '抢购积分包的用户id',\n" +
" integral_package_id bigint unsigned not null comment '抢购的积分包id',\n" +
" create_time timestamp default CURRENT_TIMESTAMP not null comment '抢购时间',\n" +
" has_use int null comment '是否使用(0未使用 1已使用)'\n" +
")\n" +
" comment '积分包订单' charset = utf8mb4\n" +
" row_format = COMPACT;\n" +
"\n" +
"create index idx_userId_integral_package_order_%d\n" +
" on integral_package_order_%d (user_id);\n";
public static void main(String[] args) {
// 生成 16 个分表 SQL
for (int i = 0; i < 16; i++) {
// 替换占位符并打印结果
String sql = String.format(SQL_TEMPLATE, i, i, i);
System.out.println(sql);
}
}
}
输出如下:
如何运行这些sql即可:
分表完成后理论上是全部配置完成了,但是你会发现运行了没报错,但是发现接口测试,发现了会出现后面的一些问题,这里我先给出解决的配置,就是在你的 WebMvcConfiguration 中加上下面那一段配置。
/**
* 参考:<a href="https://hafuhafu.com/archives/springboot-jackson-xml-result-json/">...</a>
* 设置默认的内容类型,并禁用检查Accept请求头,服务端就会忽视原有请求的Accept中的内容协商类型,而按照配置的默认的类型进行响应。
* 按以下配置的情况下,请求默认总是返回JSON。
* 如果想让部分接口只返回XML格式的数据,可以在@RequestMapping中配置produces,
* 如@GetMapping(value = "/api/users",produces = MediaType.APPLICATION_XHTML_XML_VALUE),那个接口就会返回XML格式的数据。
* @param configurer 配置器
*/
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer
.defaultContentType(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.APPLICATION_XHTML_XML)
.ignoreAcceptHeader(true);
}
4、解决问题
这里只是讲述第三点最后部分解决问题的一些方案,如果不想深入了解可以直接看测试分表那边。
傻眼了,为什么是xml格式,也就是下面这类型的问题:
Spring 5 -jackson-dataformat-xml forces @ResponseBody with XML
结果是XML格式,而不是我们希望的Json格式,我们之前配置的Json序列化失效了吗,这个是我当时的怀疑,其实这个也还是好解决的,只困扰了我一小会,如何去搜,下面先给出解决办法。
我在 WebMvcConfiguration 加了这个一个配置,然后运行解决了
成功输出了Json格式的返回。
下面是我去Github搜的一个解决方案:
shardingsphere-jdbc-5.5.0 causes spring boot to return JSON data as XML · Issue #31203 · apache/shardingsphere · GitHub
这个大佬遇到的问题和我的一样,下面就有很多好友有做评论区解决,大概都聚集在5.5.0版本的shardingsphere让WebMVC的Json转换器失效,因为他自带了一个jackson-dataformat-xml这个东西
这个大佬有提到移除那个依赖,我下面试了这样子做:
结果却报了另外的错误。。。可能是我没完全理解意思,还有另外的策略。
最后我采用了这个策略,再加一个配置
阅读了那个大佬多年工作经验的解决办法,成功加了一个配置解决了那个问题,但是在我的项目里面就多了两个Json序列化,有点在shi山上又多加了几行的感觉。。。
但是还是解决了,但是发现接口文档出现下面这个问题
我们主打的就是一个遇到问题解决问题,再次改造,上面的问题可能是优先级比接口文档高导致接口文档失效,这里就不调整优先级,换了另外一个解决方案,如果不使用接口文档那样子还是可以的。
将上面的配置换成:
/**
* 参考:<a href="https://hafuhafu.com/archives/springboot-jackson-xml-result-json/">...</a>
* 设置默认的内容类型,并禁用检查Accept请求头,服务端就会忽视原有请求的Accept中的内容协商类型,而按照配置的默认的类型进行响应。
* 按以下配置的情况下,请求默认总是返回JSON。
* 如果想让部分接口只返回XML格式的数据,可以在@RequestMapping中配置produces,
* 如@GetMapping(value = "/api/users",produces = MediaType.APPLICATION_XHTML_XML_VALUE),那个接口就会返回XML格式的数据。
* @param configurer 配置器
*/
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer
.defaultContentType(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.APPLICATION_XHTML_XML)
.ignoreAcceptHeader(true);
}
参考了这个文档:内容协商:SpringBoot添加jackson-xml后浏览器返回值的变化 – 败犬不需要安可
mark一下大佬的解决方案。
5、测试分表
这里从测试结合我之前做过的这些博客食用更佳:
对自己关于秒杀功能的一次访谈与实战-CSDN博客
基于之前的秒杀功能的优化(包括Sentinel在SpringBoot中的简单应用)-CSDN博客
1.添加积分包
这里已经是可以看到ShardingSphere生效了。
2.查询所有积分包
3.启用禁用积分包
redis中如下:
可以看到库存已经存在,为100.
4.用户查看所有秒杀积分包信息
5.根据积分包id查询库存
6.用户秒杀抢购积分包
在数据库中可以看出已经存在一条数据了
redis中库存也-1
订单信息也存在
7.用户查看自己的积分包订单
8.禁用积分包
9.删除积分包
删除成功
10.压测
由于上诉是连接服务器的数据库进行测试,下面用本地数据库进行压测
jmeter配置:
开始压测:
如下可见,已经分别分布在各个表上,分表成功:
二、还有一些遇到过的bug
之前用的是下面这个依赖,遇到各种形形色色的问题,可能是版本不服的问题吧,如果有大佬知道怎么解决可以评论区说说,还有上面为什么不能使用HashMod这个算法的原因......
<!-- https://mvnrepository.com/artifact/org.apache.shardingsphere/shardingsphere-jdbc-core -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core</artifactId>
<version>5.3.2</version>
</dependency>