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 ...
随机推荐
- JavaScript算法---基础排序类
<html> <script> //正序排序,把大的放到最后,arr[j]>arr[j+1] let fz=(arr)=>{ for(let len=arr.len ...
- java学习之旅(day.18)
网络编程 概述 计算机网络:自己百度吧 网络编程的目的:传播交流信息.数据交换.通信 想要达到这个效果需要什么: 如何准确的定位网络上的一台主机 端口 定位到这个计算机上的某个资源 找到了这个主机,如 ...
- GROUP BY clause and contains nonaggregated 报错处理
1055 - Expression #16 of SELECT list is not in GROUP BY clause and contains nonaggregated column 报错处 ...
- VisioForge.DotNet.Core.UI.WPF WPF摄像头 UVC 显示 支持 .net core
Sample applications available at https://github.com/visioforge/.Net-SDK-s-samples . Please add Visio ...
- 不到200行用Vue实现类似Swiper.js的轮播组件
前言 大家在开发过程中,或多或少都会用到轮播图之类的组件,PC和Mobile上使用 Swiper.js ,小程序上使用swiper组件等. 本文将详细讲解如何用Vue一步步实现的类似Swiper.js ...
- 009. gitlab备份和恢复
gitlab备份 #1. 创建添加配置文件 vim /etc/gitlab/gitlab.rb 文件尾添加: gitlab_reils['backup_path'] = '/data/backup/g ...
- Redis 常用的数据结构简介与实例测试【Redis 系列二】
〇.都有哪些数据结构? Redis 提供了较为丰富的数据类型,常见的有五种:String(字符串),Hash(哈希),List(列表),Set(集合).Zset(有序集合). 随着 Redis 版本的 ...
- jquery浏览器的上卷高度 节点的创建和写入 节点的删除
// js 的兼容语法 // let scrollT = document.documentElement.scrollTop || document.body.scrollTop; ...
- C#.NET Winform承载WCF RESTful API (硬编码配置)
1.新建一个名为"WindowsForms承载WCF"的WINFORM程序. 2.在解决方案里添加一个"WCF 服务库"的项目,名为"WcfYeah& ...
- jenkins结合远程仓库
既然是持续集成,对代码进行构建,我们得获取代码仓库的内容,这里选择我们搭建的gitlab服务器 1.开发工程师的机器 1. 在window上生成ssh-key $ ssh-keygen.exe -t ...