前序
- 引入Sharding JDBC jar 包
- 配置分表策略
- 动态建表
- 分表之后的查询
- 总结
项目需求:项目每日约有70万单,运行了近四个月,已经有九千多万条数据,单表存储经常会出现事务异常,查询统计慢等问题, 自然就有了按月动态分表的需求(无须分库)。
分库分表相关的技术,目前主流的有两种。一种是在服务端,如Mycat中间件;另一种在客户端,如Sharding JDBC。(注意:这里的服务端、客户端是相对我们的应用而言)
我这里主讲Sharding JDBC,进入正题前,我们先来看一下Sharding-JDBC的整体架构图:
废话少说,进入正题
引入Sharding JDBC jar 包
我使用的版本是:
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc</artifactId>
<version>4.0.0</version>
<type>pom</type>
</dependency>
<druid.version>1.1.21</druid.version>
注意版本的搭配,尽量都使用最新的稳定版,否则可能会有不兼容的问题
配置分表策略
分表算法 SingleKeyDynamicModuloTableShardingAlgorithm:
/**
* @FileName SingleKeyDynamicModuloTableShardingAlgorithm.java
* @Description:
*
* @Date 2020年2月13日
* @author LiYiShi
*
*/
@RequiredArgsConstructor
public class SingleKeyDynamicModuloTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Date> {
private final String tablePrefix;
@Override
public String doEqualSharding(Collection<String> availableTargetNames, ShardingValue<Date> shardingValue) {
SimpleDateFormat formatter = new SimpleDateFormat("yyyyMM");
return tablePrefix +formatter.format(shardingValue.getValue());
}
@Override
public Collection<String> doInSharding(Collection<String> availableTargetNames, ShardingValue<Date> shardingValue) {
// TODO Auto-generated method stub
Collection<String> result = new LinkedHashSet<>(shardingValue.getValues().size());
SimpleDateFormat formatter = new SimpleDateFormat("yyyyMM");
for (Date value : shardingValue.getValues()) {
result.add(tablePrefix + formatter.format(value));
}
return result;
}
@Override
public Collection<String> doBetweenSharding(Collection<String> availableTargetNames, ShardingValue<Date> shardingValue) {
// TODO Auto-generated method stub
Collection<String> result = new LinkedHashSet<>();
DateFormat sdf = new SimpleDateFormat("yyyyMM");
Range<Date> ranges = shardingValue.getValueRange();
Date startTime = ranges.lowerEndpoint();
Date endTime = ranges.upperEndpoint();
Calendar cal = Calendar.getInstance();
while (startTime.getTime()<=endTime.getTime()){
result.add(tablePrefix + sdf.format(startTime));
cal.setTime(startTime);//设置起时间
cal.add(Calendar.MONTH,1);
startTime = cal.getTime();
}
return result;
}
}
给数据源配置分库分表的策略,这里我采用的是硬编码的方式。当然你也可以配置在yml文件里
@Bean(name = "dtsDataSource")
@Qualifier("dtsDataSource")
public DataSource dtsDataSource() {
// 1.设置分库映射
Map<String, DataSource> dataSourceMap = new HashMap<>(2);
DruidDataSource dtsDataSource = new DruidDataSource();
//这里会自动注入数据库的连接配置,如url、username、password等
dtsDbProperties.getDruidDataSource(dtsDataSource);
dataSourceMap.put("dtsDataSource", dtsDataSource);
// 设置默认db为dtsDataSource,也就是为那些没有配置分库分表策略的指定的默认库
// 如果只有一个库,也就是不需要分库的话,map里只放一个映射就行了,只有一个库时不需要指定默认库,
// 但2个及以上时必须指定默认库,否则那些没有配置策略的表将无法操作数据
DataSourceRule rule = new DataSourceRule(dataSourceMap, "dtsDataSource");
// 2.设置分表映射
TableRule requestTableRule = TableRule.builder("dts_jps_request")
.tableShardingStrategy(new TableShardingStrategy("timestamp",
new SingleKeyDynamicModuloTableShardingAlgorithm("dts_jps_request_")))
.dataSourceRule(rule).dynamic(true).build();
TableRule policyTableRule = TableRule.builder("dts_jps_policy")
.tableShardingStrategy(new TableShardingStrategy("createdtime",
new SingleKeyDynamicModuloTableShardingAlgorithm("dts_jps_policy_")))
.dataSourceRule(rule).dynamic(true).build();
TableRule policyAttachTableRule = TableRule.builder("dts_jps_policy_attach")
.tableShardingStrategy(new TableShardingStrategy("createdtime",
new SingleKeyDynamicModuloTableShardingAlgorithm("dts_jps_policy_attach_")))
.dataSourceRule(rule).dynamic(true).build();
ShardingRule shardingRule = ShardingRule.builder().dataSourceRule(rule)
.tableRules(Arrays.asList(requestTableRule, policyTableRule, policyAttachTableRule))
.build();
// 创建数据源
DataSource dataSource = ShardingDataSourceFactory.createDataSource(shardingRule);
customDataSources.put("dtsDataSource", dataSource);
return dataSource;
}
到这里整个分表的策略配置就完成了,你会发现复杂的分表场景,用框架来解决会非常简单。
动态建表
上面完成了程序里的动态路由表,但是我们的数据库表并没有动态的创建。你可以一次性创建未来一年的表,也可以用定时任务定时提前创建下个月的表。建表可参考如下sql:
-- ****** 年月,在程序里动态替换
CREATE TABLE IF NOT EXISTS `dts_jps_policy_******` LIKE `dts_jps_policy`
分表之后的查询
按照上面的实现方式,分表之后的查询需要注意一点:查询条件必须包含有分片键,否则会报异常。其他就正常开发就行,开发上和没分片之前没啥区别
总结
到此动态按月分表的需求就开发完成了,整体来说还比较简单,重点在于能够合理的根据自身的业务需求去实现自己的分表逻辑