1.走过的弯路
在研究分表的时候,我他喵的是真的体会到了"浩如烟海"是啥意思.要么就是资料太老,要么就很少是按照月份分表的. 中间我走了很多弯路,甚至一度怀疑jap不适合用来进行分表.
从我看过的资料来说,没有一个博客介绍使用Shardingsphere进行分表是思路是什么样的,这也是我走了很多弯路的原因.
先介绍背景: 单个数据库,按照月份进行分表,每个表有月份的后缀,类似于: table_202006.
我先说下我的误区, 一开始我以为分表查询只需要配置库级别的分表规则就行了.这种想法是错误的,Shardingsphere是按照表级别进行配置的, 也就是说你想要分表查询哪个就配置对应表的分表规则. 其他没有配置分表规则的表则还是按照基本的表名进行查询.
还有一个坑, 一般我们会打印sql, 引入Shardingsphere之后,原本的打印的sql并不会是最后路由的表.举个栗子, 我们原本的查询"select * from base". 分表之后我们的查询最后路由到了base_202006表上面, 但是sql显示的还是"select * from base". 需要我们开启Shardingsphere的日志才能看到最终的路由.
2.Shardingsphere
2.1引入依赖
具体的版本,根据时间自行更新
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>
2.2 application-dev.yml
看到这个名字,相信大家应该都知道了还有一个application.yml配置.分表的关键配置都在application-dev.yml, 所以直贴这个配置的内容了.
#配置数据源
spring:
#配置 Jpa
jpa:
hibernate:
# 生产环境设置成 none,避免程序运行时自动更新数据库结构
ddl-auto: none
shardingsphere:
datasource:
names: db0 # 数据源名称
db0:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/database?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false
username: root
password: password
sharding:
tables:
base: # 表名
actual-data-nodes: db0.base_$->{2019..2020}0$->{1..9}, db0.base_$->{2019..2020}$->{10..12}
table-strategy:
standard:
sharding-column: date # 分片键
precise-algorithm-class-name: com.bihu.kankan.config.TableShardingAlgorithm # 实现类的完全限定类名
range-algorithm-class-name: com.bihu.kankan.config.TableRangeShardingAlgorithm
props:
sql:
show: true
整个配置的重点都在shardingsphere
这个级别下面,我来一个个的解释:
- datasource: 数据库连接的配置. url这个配置如果是使用阿里云的连接可能需要改成jdbc-url
- names: 自定义的数据源名称
- db0: 就是刚刚配置的数据源名称
- sharding.tables.base: base就是数据库中需要分表查询的表名
- actual-data-nodes: 这个是重点,需要inline表达式进行配置. 上面的配置意思是:查询数据源db0下面2019年-2020年的base表. 这个规则可以查看官网的说明 行表达式
- sharding-column: base表中的字段,在查询的时候需要传入这个字段,然后根据自己定义的分表规则判断应该路由到那张表上. 不仅仅是date字段, 也可以是snowflake算法生成的id字段(因为可以根据id字段反推数据入库时间). 还可以是自增id字段(模运算决定路由的表)等等.
- precise-algorithm-class-name: 自定义的分表逻辑实现类, 这里我们指定的是精确查询,即sql中是
=
的时候会触发此规则. - range-algorithm-class-name: 复合分片策略, 提供对 SQL 语句中的 =, >, <, >=, <=, IN 和 BETWEEN AND 的分片操作支持
- props.sql.show: 展示路由后的sql, 前面已经说了,这是个坑.
3.自定义的分表规则实现类
PreciseShardingAlgorithm<Long>
中的泛型类型和上面指定的sharding-column
类型有关
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
/**
* 根据当前的月份进行分表查询
* @author LiYilin
*/
@Component
public class TableShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMM");
@Override
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> preciseShardingValue) {
// 基本的表名_年份月份 base_199001
String targetTable = preciseShardingValue.getLogicTableName() + "_" + LocalDate.now().format(formatter);
if (availableTargetNames.contains(targetTable)){
return targetTable;
}
throw new UnsupportedOperationException("无效的表名称: " + targetTable);
}
}
TableRangeShardingAlgorithm.java
import com.google.common.collect.Range;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collection;
@Component
public class TableRangeShardingAlgorithm implements RangeShardingAlgorithm<Integer> {
@Override
public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Integer> rangeShardingValue) {
Collection<String> collect = new ArrayList<>();
Range<Integer> valueRange = rangeShardingValue.getValueRange();
//TODO 这种写法只支持between, 但是效率很高
String between = rangeShardingValue.getLogicTableName() + "_" + String.valueOf(valueRange.lowerEndpoint()).substring(0, 6);
String and = rangeShardingValue.getLogicTableName() + "_" + String.valueOf(valueRange.upperEndpoint()).substring(0, 6);
for (String each : collection) {
if (between.equals(each) || and.equals(each)) {
collect.add(each);
}
}
return collect;
}
}
至此,一个分表操作基本完成.