ShardingSphere简介
ShardingSphere 由 JDBC、Proxy 和 Sidecar(规划中)这 3 款既能够独立部署,又支持混合部署配合使用的产品组成。 它们均提供标准化的基于数据库作为存储节点的增量功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。
关系型数据库当今依然占有巨大市场份额,是企业核心系统的基石,未来也难于撼动,我们更加注重在原有基础上提供增量,而非颠覆。
ShardingSphere-JDBC
定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。
-
适用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC;
-
支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, HikariCP 等;
-
支持任意实现 JDBC 规范的数据库,目前支持 MySQL,PostgreSQL,Oracle,SQLServer 以及任何可使用 JDBC 访问的数据库。
总结 兼容性好 , 目前主流的ORM框架均能够支持、而且只需要在pom文件中引入依赖即可、使用起来非常方便。
查看更多关于分库分表、读写分离:https://mp.weixin.qq.com/s/aFXZ8rT9g4oj3ZnioC2vkg
添加依赖
<!-- 关系型数据库中间件sharding -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.1</version>
</dependency>
配置数据源
@Configuration
public class DruidConfig{
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dataSource(DataSource masterDataSource)
{
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
setDataSource(targetDataSources, DataSourceType.DIROSS.name(), "shardingDataSource");
return new DynamicDataSource(masterDataSource, targetDataSources);
}
@Bean
@ConfigurationProperties("spring.shardingsphere.datasource.master")
public DataSource ossDataSource(DruidProperties druidProperties)
{
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
return druidProperties.dataSource(dataSource);
}
@Bean(name = "shardingDataSource")
public DataSource shardingDataSource(@Qualifier("ossDataSource") DataSource ossDataSource) throws SQLException
{
Map<String, DataSource> dataSourceMap = new HashMap<>();
dataSourceMap.put("master", ossDataSource);//这里指:spring.shardingsphere.datasource.names=master
// 配置分片规则
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
// ts_dir_oss表规则在yml中已配置,代码写法如下:
/*
TableRuleConfiguration tableRuleConfig = new TableRuleConfiguration("ts_dir_oss", "master.ts_dir_oss_$->{2021}_$->{1..3}_$->{1..2}");
// 配置分布式主键生成策略:雪花算法
tableRuleConfig.setKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE", "id") );
// 配置分表策略
tableRuleConfig.setTableShardingStrategyConfig(new StandardShardingStrategyConfiguration("point_time", new PointTimePreciseShardingAlgorithm()));
shardingRuleConfig.getTableRuleConfigs().add(tableRuleConfig);
*/
// 获取数据源对象
DataSource dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, new Properties());
return dataSource;
}
}
/**
* 数据源
*/
public enum DataSourceType
{
/**
* 主库
*/
MASTER,
/**
* 从库
*/
SLAVE,
/**
* 分表
*/
DIROSS
}
/**
* 测试查询分表数据
*/
@DataSource(DataSourceType.DIROSS)
@GetMapping("/test")
public void test() {
// 查询分表数据表时切换数据源
}
添加yml配置
spring:
main:
# 一个实体类对应多张表,必须设置这个
allow-bean-definition-overriding: true
shardingsphere:
props:
sql:
# 打印SQL
show: false
datasource:
names: master
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/times_tool?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456
sharding:
tables:
ts_dir_oss:
# 配置表规则
actual-data-nodes: master.ts_dir_oss_$->{2013..2023}_$->{(1..12).collect{t ->t.toString().padLeft(2,'0')}}_$->{(1..2).collect{t ->t.toString().padLeft(2,'0')}}
# 配置主键id生成策略,指定雪花算法
key-generator:
column: id
type: SNOWFLAKE
table-strategy:
standard:
# 分片字段
sharding-column: point_time
# 标准策略 + 精确分片算法 SQL就是(=或in)
precise-algorithm-class-name: com.cn.framework.datasource.PointTimePreciseShardingAlgorithm
# 标准策略 + 范围分片算法 主要是(between A and B)
range-algorithm-class-name: com.cn.framework.datasource.PointTimeRangeShardingAlgorithm
# 复合分片策略 提供对SQL语句中的(=,in,beteewn A and B)的分片操作支持
# complex:
# sharding-columns: point_time
# algorithm-class-name: com.cn.framework.datasource.ComplexShardingAlgorithm
代码实现:一张表存储半月数据
复合分片策略
/**
* 复合分片策略 提供对SQL语句中的(=,in,beteewn A and B)的分片操作支持.
*/
@Slf4j
public class ComplexShardingAlgorithm implements ComplexKeysShardingAlgorithm {
@Override
public Collection<String> doSharding(Collection collection, ComplexKeysShardingValue complexKeysShardingValue) {
Set<String> tables = new HashSet<>();
Map<String, Range<String>> rangeMap = complexKeysShardingValue.getColumnNameAndRangeValuesMap();
if(CollUtil.isNotEmpty(rangeMap)){
for(String s : rangeMap.keySet()){
Range<String> valueRange = rangeMap.get(s);
if(valueRange!=null){
Date begin = DateUtil.parse(valueRange.lowerEndpoint(), DateUtils.YYYY_MM);
Date end = DateUtil.parse(valueRange.upperEndpoint(),DateUtils.YYYY_MM);
log.info("lowerSuffix:{},upperSuffix:{}",begin,end);
for(Object tab:collection){
String tableName = (String)tab;
String dateStr = StrUtil.subAfter(tableName, "ts_dir_oss_", true).replace("_", "-");
Date date = DateUtil.parse(dateStr,DateUtils.YYYY_MM);
if((date.after(begin) || date.equals(begin)) && (date.before(end) || date.equals(end) )){
tables.add(tableName);
}
}
}
}
}
Map<String, List<String>> map = complexKeysShardingValue.getColumnNameAndShardingValuesMap();
if(CollUtil.isNotEmpty(map)){
for(String s : map.keySet()){
List<String> list = map.get(s);
if(list.size()>0){
String v = list.get(0);
Date date = DateUtil.parseDate(v);
String suffix = getSuffixByDate(date);
for(Object tab: collection){
String tableName = (String)tab;
if(tableName.endsWith(suffix)){
tables.add(tableName);
}
}
}
}
}
log.info("match tableNames:{}", tables.toString());
return tables;
}
public String getSuffixByDate(Date date){
String dateStr = DateUtil.format(date,"yyyy_MM");
int day = DateUtil.date(date).dayOfMonth();
if(day<=15) {
return dateStr + "_01";
}else{
return dateStr + "_02";
}
}
}
标准策略+精确分片算法
/**
* 标准策略 + 精确分片算法 SQL就是(=或in)
*/
@Slf4j
public class PointTimePreciseShardingAlgorithm implements PreciseShardingAlgorithm<String> {
@Override
public String doSharding(Collection<String> availableTargetName, PreciseShardingValue<String> preciseShardingValue) {
Date date = DateUtil.parseDate(preciseShardingValue.getValue());
String suffix = getSuffixByDate(date);
for(String tabName: availableTargetName){
if(tabName.endsWith(suffix)){
log.info("match tableName:{}", tabName);
return tabName;
}
}
throw new IllegalArgumentException("未找到匹配的数据表");
}
public String getSuffixByDate(Date date){
String dateStr = DateUtil.format(date,"yyyy_MM");
int day = DateUtil.date(date).dayOfMonth();
if(day<=15) {
return dateStr + "_01";
}else{
return dateStr + "_02";
}
}
}
标准策略+范围分片算法
/**
* 标准策略 + 范围分片算法 主要是(between A and B)
*/
@Slf4j
public class PointTimeRangeShardingAlgorithm implements RangeShardingAlgorithm<String> {
@Override
public Collection<String> doSharding(Collection<String> availableTargetName, RangeShardingValue<String> rangeShardingValue) {
Range<String> valueRange = rangeShardingValue.getValueRange();
Date begin = DateUtil.parse(valueRange.lowerEndpoint(), DateUtils.YYYY_MM);
Date end = DateUtil.parse(valueRange.upperEndpoint(),DateUtils.YYYY_MM);
log.info("lowerSuffix:{},upperSuffix:{}",begin,end);
Set<String> tables = new HashSet<>();
for(String tableName:availableTargetName){
String dateStr = StrUtil.subAfter(tableName, "ts_dir_oss_", true).replace("_", "-");
Date date = DateUtil.parse(dateStr,DateUtils.YYYY_MM);
if((date.after(begin) || date.equals(begin)) && (date.before(end) || date.equals(end) )){
tables.add(tableName);
}
}
log.info("match tableNames:{}", tables.toString());
return tables;
}
}
批量生成数据表
SQL脚本
table.template
CREATE TABLE IF NOT EXISTS ts_dir_oss_${tableSuffix} (
`id` BIGINT(20) AUTO_INCREMENT NOT NULL,
`nodes_id` bigint(20) DEFAULT NULL COMMENT '目录节点id',
`point_id` varchar(48) DEFAULT NULL COMMENT '测点名称',
`point_time` varchar(18) DEFAULT NULL COMMENT '测点时间',
`path` varchar(225) DEFAULT NULL COMMENT 'OSS地址',
`size` bigint(11) DEFAULT NULL COMMENT '文件大小',
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_point_id` (`point_id`),
KEY `index_nodes_id` (`nodes_id`),
KEY `index_point_time` (`point_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
根据时间范围批量创建数据表
引入依赖
<!-- velocity代码生成使用模板 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>${velocity.version}</version>
</dependency>
代码实现
@RestController
@RequestMapping("/tools")
@Api(tags = "工具")
public class InitController
{
@Autowired
private JdbcTemplate jdbcTemplate;
@GetMapping("createTable")
@ApiOperationSupport(order = 1)
@ApiOperation("批量创建数据库分表")
public AjaxResult createTable(@NotEmpty(message = "开始日期不能为空") String beginDate,@NotEmpty(message = "结束日期不能为空") String endDate) throws Exception{
Date date = DateUtil.parseDate(beginDate);
while(true) {
if(date.after(DateUtil.parseDate(endDate))) {
break;
}
for(int i = 1; i <= 2; i++) {
VelocityInitializer.initVelocity();
VelocityContext context = new VelocityContext();
String tableSuffix = DateUtil.format(date, "yyyy_MM");
context.put("tableSuffix", tableSuffix + "_0" + i);
Template template = Velocity.getTemplate("template/table.template");
StringWriter sw = new StringWriter();
template.merge(context, sw);
jdbcTemplate.execute(sw.toString());
}
date = DateUtils.addMonths(date, 1);
}
return AjaxResult.success();
}
}
注意事项
- sharding不支持聚合查询,如 group by统计数据,如果要有统计数据需求,可以用一个统计表实时或定时实现数据同步。
- 数据表需要根据切片规则提前创建好,否则程序会报错提示找不到指定表。