SpringBoot集成ShardingSphere分表中间件
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.SHARDING.name(), "shardingDataSource");
return new DynamicDataSource(masterDataSource, targetDataSources);
}
}
/**
* 数据源
*/
public enum DataSourceType
{
/**
* 主库
*/
MASTER,
/**
* 从库
*/
SLAVE,
/**
* 分表
*/
SHARDING
}
/**
* 在Mapper类中配置分表数据源
*/
@DataSource(DataSourceType.SHARDING)
public interface TestMapper {
// 针对分表的所有增删改查操作,都要重新写对应的SQL语句,否则直接mybatis接口不会生效
}
添加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;
}
}
动态新增数据表脚本
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 '地址',
`size` bigint(11) DEFAULT NULL COMMENT '文件大小',
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_point_time` (`point_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
动态更新分片策略
@Component
public class ShardingTableRuleActualTablesRefresh {
private Logger logger = LoggerFactory.getLogger(ShardingTableRuleActualTablesRefresh.class);
@Resource(name = "shardingDataSource")
private DataSource dataSource;
@PostConstruct
public void initData(){
try {
List<String> sdList = new ArrayList<>();
// 启动项目时初始化分片规则,查出分片配置表信息
actualTablesRefresh(sdList);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 动态刷新变量表的分片策略, 核心通过反射更新tableRule
* @param idList
*/
public void actualTablesRefresh(List<String> sdList) {
try {
logger.info("-----开始刷新表节点-----");
if(CollUtil.isEmpty(idList)){
logger.error("传入idList参数为空");
return;
}
ShardingDataSource shardingDataSource = (ShardingDataSource)dataSource;
//运行时获取分片规则
ShardingRule rule = shardingDataSource.getRuntimeContext().getRule();
//获取分表策略集合
Collection<TableRule> tableRules = rule.getTableRules();
for (TableRule tableRule : tableRules) {
//获取真实节点
List<DataNode> actualDataNodes = tableRule.getActualDataNodes();
Field actualDataNodesField = TableRule.class.getDeclaredField("actualDataNodes");
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(actualDataNodesField, actualDataNodesField.getModifiers() & ~Modifier.FINAL);
//数据源名
String dataSourceName = actualDataNodes.get(0).getDataSourceName();
//逻辑表名
String logicTableName = tableRule.getLogicTable();
//根据真实业务,新增节点的逻辑
for (String sd : sdList) {
actualDataNodes.add(new DataNode(dataSourceName+"."+logicTableName+"_" + sd));
}
actualDataNodesField.setAccessible(true);
actualDataNodesField.set(tableRule, actualDataNodes);
Set<String> actualTables = Sets.newHashSet();
Map<DataNode, Integer> dataNodeIntegerMap = Maps.newHashMap();
//更新actualTables、dataNodeIntegerMap
AtomicInteger a = new AtomicInteger(0);
actualDataNodes.forEach((dataNode -> {
actualTables.add(dataNode.getTableName());
if (a.intValue() == 0){
a.incrementAndGet();
dataNodeIntegerMap.put(dataNode, 0);
}else {
dataNodeIntegerMap.put(dataNode, a.intValue());
a.incrementAndGet();
}
}));
//动态刷新:actualTables
Field actualTablesField = TableRule.class.getDeclaredField("actualTables");
actualTablesField.setAccessible(true);
actualTablesField.set(tableRule, actualTables);
//动态刷新:dataNodeIndexMap
Field dataNodeIndexMapField = TableRule.class.getDeclaredField("dataNodeIndexMap");
dataNodeIndexMapField.setAccessible(true);
dataNodeIndexMapField.set(tableRule, dataNodeIntegerMap);
//动态刷新:datasourceToTablesMap
Map<String, Collection<String>> datasourceToTablesMap = Maps.newHashMap();
datasourceToTablesMap.put(dataSourceName, actualTables);
Field datasourceToTablesMapField = TableRule.class.getDeclaredField("datasourceToTablesMap");
datasourceToTablesMapField.setAccessible(true);
datasourceToTablesMapField.set(tableRule, datasourceToTablesMap);
logger.info("-----------------end----------------");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意事项
- sharding不支持部分复杂的聚合查询,如果要有统计数据需求,可以用一个统计表实时或定时实现数据同步。
- 数据表需要根据切片规则提前创建好,否则程序会报错提示找不到指定表。
SpringBoot集成ShardingSphere分表中间件的更多相关文章
- 一文快速入门分库分表中间件 Sharding-JDBC (必修课)
书接上文 <一文快速入门分库分表(必修课)>,这篇拖了好长的时间,本来计划在一周前就该写完的,结果家庭内部突然人事调整,领导层进行权利交接,随之宣布我正式当爹,紧接着家庭地位滑落至第三名, ...
- 支付宝分库分表中间件--zdal简介
中间件, 如果仅仅作为一名用户的话, 主要关注一下如何使用即可, 大多数情况下也就是配置. 下面简单的介绍一下支付宝的分库分表中间件--->zdal在web项目中的配置. 1, 在网上查阅相关资 ...
- 当当开源sharding-jdbc,轻量级数据库分库分表中间件
近期,当当开源了数据库分库分表中间件sharding-jdbc. Sharding-JDBC是当当应用框架ddframe中,从关系型数据库模块dd-rdb中分离出来的数据库水平分片框架,实现透明化数据 ...
- 解读分库分表中间件Sharding-JDBC
[编者按]数据库分库分表从互联网时代开启至今,一直是热门话题.在NoSQL横行的今天,关系型数据库凭借其稳定.查询灵活.兼容等特性,仍被大多数公司作为首选数据库.因此,合理采用分库分表技术应对海量数据 ...
- 分库分表中间件Sharding-JDBC
数据库分库分表从互联网时代开启至今,一直是热门话题.在NoSQL横行的今天,关系型数据库凭借其稳定.查询灵活.兼容等特性,仍被大多数公司作为首选数据库.因此,合理采用分库分表技术应对海量数据和高并发对 ...
- 分库分表中间件sharding-jdbc的使用
数据分片产生的背景,可以查看https://shardingsphere.apache.org/document/current/cn/features/sharding/,包括了垂直拆分和水平拆分的 ...
- springboot+jpa分库分表项目实例
分库分表场景 关系型数据库本身比较容易成为系统瓶颈,单机存储容量.连接数.处理能力都有限.当单表的数据量达到1000W或100G以后,由于查询维度较多,即使添加从库.优化索引,做很多操作时性能仍下降严 ...
- Sharding-JDBC基本使用,整合Springboot实现分库分表,读写分离
结合上一篇docker部署的mysql主从, 本篇主要讲解SpringBoot项目结合Sharding-JDBC如何实现分库分表.读写分离. 一.Sharding-JDBC介绍 1.这里引用官网上的介 ...
- Mysql系列五:数据库分库分表中间件mycat的安装和mycat配置详解
一.mycat的安装 环境准备:准备一台虚拟机192.168.152.128 1. 下载mycat cd /softwarewget http:-linux.tar.gz 2. 解压mycat tar ...
- Mycat 数据库分库分表中间件
http://www.mycat.io/ Mycat 国内最活跃的.性能最好的开源数据库中间件! 我们致力于开发高性能的开源中间件而努力! 实体书Mycat权威指南 »开源投票支持Mycat下载 »s ...
随机推荐
- 超级简单实用的CSS3动画,增添网页效果
有时候做网页,如果都写成静态的没有动态效果,犹如一张张图片,没有视觉感受,没有达到很好的视觉效果. 其实一些简单的CSS3动画,可以增添网页的动态感,使自己设计的网页更有视觉享受.1.图片有一定角度的 ...
- 动态修改manifest.json
点击查看代码 // h5开发环境 const h5Dev = { baseUrl: 'https://devh5.....' } // h5测试环境 const h5Test= { baseUrl: ...
- 4G EPS 中建立 UE 和 MME 之间的 NAS(非接入服务)信令连接
目录 文章目录 目录 前文列表 建立 NAS(非接入服务)信令连接 前文列表 <4G EPS 中的小区搜索> <4G EPS 中的 PLMN 选择> <4G EPS 中的 ...
- dbeaver使用详解
1.dbeaver使用本地驱动 解压 点击可执行文件 驱动管理 新建驱动 起名字 com.mysql.jdbc.Driver jdbc:mysql//{host}[:{port}]/[{databas ...
- .NET Aspire 正式发布:简化 .NET 云原生开发
.NET团队北京时间2024年5月22日已正式发布.NET Aspire ,在博客文章里做了详细的介绍:.NET Aspire 正式发布:简化 .NET 云原生开发 - .NET 博客 (micros ...
- StackOverFlow & OutOfMemory
StackOverFlow & OutOfMemory 两者都为 Error,广义上的"异常" StackOverflow 通常为 Java 虚拟机栈内存不够,JVM 对方 ...
- 企业级nginx内核优化篇
1. cpu核心配置 方法1: worker_processes auto; 自动调用[推荐] 方法2: worker_processes 4; 手工配置 检查CPU核心:yum install nu ...
- Specs satisfying the `flutter_twitter (from `.symlinks/plugins/flutter_twitter/ios`)` dependency were found, but they required a higher minimum deployment target
MXFlutter的依赖发生变化,需要将iOS最低系统版本提高 修改之后,进入到MXFlutter目录,执行 flutter run 发现编译错误 咨询了下,mxflutter需要flutter为 用 ...
- React脚手架 创建React项目
React团队主要推荐使用create-react-app来创建React新的单页应用项目的最佳方式. React脚手架(create-react-app)意义 脚手架是官方提供,零配置,无需手动配置 ...
- ETL工具-nifi干货系列 第十四讲 nifi处理器PublishKafka实战教程
1.kettle的kafka生产者叫kafka producer,nifi中的相应处理器为PublishKafka,如下图所示: 可以很清楚的看到PublishKafka处理器支持多个版本的kafka ...