最近在学习使用 shardingsphere 分表,定位为轻量级 Java 框架,无需部署额外服务,引入 Jar 包即可使用;当前使用发布最新版本 4.0.0,新特性查看:ShardingSphere 4.0.0
pom 文件引入maven依赖:
<dependency><groupId>org.apache.shardingsphere</groupId><artifactId>sharding-jdbc-spring-boot-starter</artifactId><version>4.0.0</version>
</dependency><!--<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>${druid-version}</version>
</dependency>--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>${druid-version}</version>
</dependency>
<druid-version>1.1.10</druid-version> ,这里使用的是 sharding-jdbc-spring-boot-starter,也可以使用 sharding-jdbc-core,版本号一致即可
YML 文件配置(也可以使用 Java 代码配置):
1. 数据源配置
spring:shardingsphere:# 数据源配置datasource:names: ds0 # ,ds1 ds0:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/ds0nameusername: rootpassword: xxxx# other params# 多数据源#ds1:# type: com.alibaba.druid.pool.DruidDataSource# driver-class-name: com.mysql.jdbc.Driver# url: jdbc:mysql://localhost:3306/ds1name# username: root# password: xxxx
我配置的是单数据源分表,也可指定多数据源进行分库策略 or 主从策略
2. 表分片策略
2.1 主键/ID字段分片
sharding:tables:sys_user: # 逻辑表actual-data-nodes: ds0.sys_user_$->{0..9} # 表数量配置table-strategy:inline:sharding-column: user_id # 分片键algorithm-expression: sys_user_$->{user_id % 10} # 根据 user_id 取模分表key-generator: # user_id 生成策略column: user_idtype: SNOWFLAKE# sys_xxx # 其他表分片
sys_user 是逻辑表名称,分表后创建的表应该是 sys_user_number
actual-data-nodes 是数据节点由数据源和数据表组成,也就是真实表,使用 行表达式 {0..10} 表示有 sys_user_0 到 sys_user_9 共 十张表;shardingsphere 不会自动创建表,需要使用 脚本定时 或 手动提前创建好
table-strategy 是 分片策略,根据需求实现具体的分片策略,inline 为行表达式,standard 为自定义分片
algorithm-expression 是算法表达式,根据 user_id 取模尾数为 n 的路由到后缀为 n 的表中(sys_user_n)
key-generator 是主键生成,SNOWFLAKE 是Twitter的分布式 ID 生成算法
2.2 时间字段分片(如日志表)
sharding:tables:sys_log: # 逻辑表actual-data-nodes: ds0.sys_log_20200116 # 默认表配置table-strategy:standard:sharding-column: log_time # 分片列preciseAlgorithmClassName: pers.allen.demo.config.SysLogDataTableShardingAlgorithm # 精确分片rangeAlgorithmClassName: pers.allen.demo.config.SysLogDataTableRangeShardingAlgorithm # 范围分片key-generator: # id 生成策略column: idtype: SNOWFLAKE
preciseAlgorithmClassName 和 rangeAlgorithmClassName 为分片支持和实现请查看 分片算法 ,下面是按天分片实现,按月分片只需要改动部分代码即可
代码如下:
/*** 通用部分*/
public class SysLogDataTableSharding {//protected static final String UNDERLINE = "_";// cure_time 日期格式protected static final DateTimeFormatter dtfTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");protected static final DateTimeFormatter dtfDate = DateTimeFormatter.ofPattern("yyyyMMdd");protected static String spliceTableName(String logicTableName,String date) {StringBuilder tableName = new StringBuilder();tableName.append(logicTableName).append(UNDERLINE).append(date);return tableName.toString();}}
/*** 精确分片* PreciseShardingAlgorithm:用于处理使用单一键作为分片键的=与IN进行分片的场景* Created by lengyul on 2020/1/8 10:11*/
public class SysLogDataTableShardingAlgorithm extends SysLogDataTableSharding implements PreciseShardingAlgorithm<String> {@Overridepublic String doSharding(Collection<String> collection, PreciseShardingValue<String> preciseShardingValue) {// 逻辑表名称String logicTableName = preciseShardingValue.getLogicTableName();// cure_time = preciseShardingValue.getValue();String cure_time = preciseShardingValue.getValue();// 将时间字符串转换为日期类型LocalDate parseDate = LocalDate.parse(cure_time, dtfTime);// 获取日期 yyyyMMddString yyyyMMdd = parseDate.format(dtfDate);// 实际表名称String realTableName = spliceTableName(logicTableName,yyyyMMdd);System.out.println(realTableName);return realTableName;}}
/*** 范围分片* RangeShardingAlgorithm:用于处理使用单一键作为分片键的BETWEEN AND、>、<、>=、<=进行分片的场景* Created by lengyul on 2020/1/8 15:06*/
public class SysLogDataTableRangeShardingAlgorithm extends SysLogDataTableSharding implements RangeShardingAlgorithm<String> {@Overridepublic Collection<String> doSharding(Collection<String> collection, RangeShardingValue<String> rangeShardingValue) {// 逻辑表名称String logicTableName = rangeShardingValue.getLogicTableName();Range<String> ranges = rangeShardingValue.getValueRange();// 获取时间范围try {String lower = ranges.lowerEndpoint();String upper = ranges.upperEndpoint();LocalDateTime startTime = LocalDateTime.parse(lower,dtfTime);LocalDateTime endTime = LocalDateTime.parse(upper,dtfTime);Collection<String> rangeTables = getRangeTables(logicTableName,startTime, endTime);System.out.println(rangeTables);return rangeTables;} catch (Exception e) {e.printStackTrace();;}return collection;}/*** LocalDateTime 计算时间表范围* @param logicTableName* @param startTime* @param endTime* @return*/private static Collection<String> getRangeTables(String logicTableName,LocalDateTime startTime, LocalDateTime endTime) {Collection<String> tables = new LinkedHashSet<>();while(startTime.isBefore(endTime)) {String yyyyMMdd = startTime.toLocalDate().format(dtfDate);String realTableName = spliceTableName(logicTableName,yyyyMMdd);tables.add(realTableName);startTime = startTime.plusDays(1);}/*当 startTime 的 HH:mm:ss 大于 endTime 时,day + 1 会出现 startTime 大于 endTime导致 endTime 对应的表没有添加到 tables 列表中 */String endDate = endTime.toLocalDate().format(dtfDate);String lastTable = spliceTableName(logicTableName,endDate);if(!tables.contains(lastTable)) {tables.add(lastTable);}return tables;}
关于查询:
- 分片后,如果查询 where 条件没有带分片字段的话会去扫描配置的所有真实数据表 = actual-data-nodes,最后将匹配到的数据合并为一个结果集返回,保存数据时找不到对应的表会报表不存在
- 复杂SQL或者UNION可能会不支持,具体 SQL使用规范
遇到问题记录:
ShardingSphere 4.0.0-RC3 之前的版本 MySQL NOW() 被解析为字符串 "NOW()",导致保存时间字段失败
ShardingSphere 4.0.0 + druid-spring-boot-starter 启动初始化数据源找不到 "url",具体原因:可能是 druid-spring-boot-starter 会去构建数据源,换为 druid 即可解决