赞
踩
业务中每隔5s推送一条数据而且最高并发量可达2w,经过计算数据量轻松过亿。一般数据库单表超过1000W性能就会急剧下降,这时候就需要做相应的分库分表处理
Shardingjdbc以jar的形式分库分表,只需要引入相关依赖并做相应配置即可完成分库分表性能优于mycat而且ShardingSphere整个生态已入驻Apache,社区也很活跃是目前主流使用的分库分表解决方式;
*使用Shardingjdbc需要注意的地方:
Shardingjdbc对业务代码的入侵性比较高,在做整合的时候有些地方需要按照Shardingjdbc官方规范改写底层的查询方式
shardingjdbc-version-4点几的版有一些SQL是不支持或者存在一些关于sql解析的问题相对较多,所以这里选择最新的5.1.1版本解决了很多不支持项和一些关于sql解析的问题
关于ShardingSphere版本4和版本5的官方说明文档已放到网盘里了有兴趣的可以研究比对一下:
https://pan.baidu.com/s/1nlQoY6Ye5wKdwek94svEKQ
提取码:hibs
这里打算分三个库,每个库31张表,以thp_gps_data表为例
bhdcm-obd-00--
|--thp_gps_data_1
|--thp_gps_data_2
|--thp_gps_data_...31
bhdcm-obd-01--
|--thp_gps_data_1
|--thp_gps_data_2
|--thp_gps_data_...31
bhdcm-obd-02--
|--thp_gps_data_1
|--thp_gps_data_2
|--thp_gps_data_...31
thp_gps_data_{1…31}依次建表,建表sql:
CREATE TABLE `thp_gps_data_1` (
`id` VARCHAR(64) NOT NULL COMMENT '主键',
`equip_no` VARCHAR(500) DEFAULT NULL COMMENT '设备号',
`instruction_value` VARCHAR(500) DEFAULT NULL COMMENT '指令值',
`timestamp` BIGINT(20) DEFAULT NULL COMMENT '时间戳',
`longitude` VARCHAR(500) DEFAULT NULL COMMENT '经度',
`latitude` VARCHAR(500) DEFAULT NULL COMMENT '纬度',
`altitude` VARCHAR(500) DEFAULT NULL COMMENT '海拔高度',
`speed` VARCHAR(500) DEFAULT NULL COMMENT '车速(km)',
`angle` VARCHAR(500) DEFAULT NULL COMMENT '方向角度',
`signal_power` VARCHAR(500) DEFAULT NULL COMMENT '信号强度(0-31)',
`create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
`update_time` DATETIME DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='obd-gps数据表';
<!--注意不要用这个依赖,他会创建数据源,跟上面ShardingJDBC的SpringBoot集成依赖有冲突 --> <!-- <dependency>--> <!-- <groupId>com.alibaba</groupId>--> <!-- <artifactId>druid-spring-boot-starter</artifactId>--> <!-- <version>1.1.20</version>--> <!-- </dependency>--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.13-SNSAPSHOT</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.11</version> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.1.1</version> </dependency>
#**************************************shardingjdbc-version-5.1.1***************************************# # 数据源参数配置 initialSize=5 minIdle=5 maxIdle=100 maxActive=20 maxWait=60000 timeBetweenEvictionRunsMillis=60000 minEvictableIdleTimeMillis=300000 # Sharding Jdbc配置 # 分库的数量 dataBaseSize=3 # 分表的数量 tableSize=31 # ds0,ds1,ds2 spring.shardingsphere.datasource.names=ds0,ds1,ds2 # 配置数据源ds0 shardingjdbc逻辑源ds0映射实际源bhdcm-obd-00 spring.shardingsphere.datasource.ds0.type=com.alibaba.druid.pool.DruidDataSource spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.ds0.url=jdbc:mysql://10.254.250.178:3306/bhdcm-obd-00?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai spring.shardingsphere.datasource.ds0.username=root spring.shardingsphere.datasource.ds0.password=root spring.shardingsphere.datasource.ds0.initialSize=${initialSize} spring.shardingsphere.datasource.ds0.minIdle=${minIdle} spring.shardingsphere.datasource.ds0.maxActive=${maxActive} spring.shardingsphere.datasource.ds0.maxWait=${maxWait} spring.shardingsphere.datasource.ds0.validationQuery=SELECT 1 FROM DUAL spring.shardingsphere.datasource.ds0.timeBetweenEvictionRunsMillis=${timeBetweenEvictionRunsMillis} spring.shardingsphere.datasource.ds0.minEvictableIdleTimeMillis=${minEvictableIdleTimeMillis} # 配置数据源ds1 shardingjdbc逻辑源ds1映射实际源bhdcm-obd-01 spring.shardingsphere.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.ds1.url=jdbc:mysql://10.254.250.178:3306/bhdcm-obd-01?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai spring.shardingsphere.datasource.ds1.username=root spring.shardingsphere.datasource.ds1.password=root spring.shardingsphere.datasource.ds1.initialSize=${initialSize} spring.shardingsphere.datasource.ds1.minIdle=${minIdle} spring.shardingsphere.datasource.ds1.maxActive=${maxActive} spring.shardingsphere.datasource.ds1.maxWait=${maxWait} spring.shardingsphere.datasource.ds1.validationQuery=SELECT 1 FROM DUAL spring.shardingsphere.datasource.ds1.timeBetweenEvictionRunsMillis=${timeBetweenEvictionRunsMillis} spring.shardingsphere.datasource.ds1.minEvictableIdleTimeMillis=${minEvictableIdleTimeMillis} # 配置数据源ds2 shardingjdbc逻辑源ds2映射实际源bhdcm-obd-02 spring.shardingsphere.datasource.ds2.type=com.alibaba.druid.pool.DruidDataSource spring.shardingsphere.datasource.ds2.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.ds2.url=jdbc:mysql://10.254.250.178:3306/bhdcm-obd-02?useUnicode=true&characterEncoding=utf8&tinyInt1isBit=false&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai spring.shardingsphere.datasource.ds2.username=root spring.shardingsphere.datasource.ds2.password=root spring.shardingsphere.datasource.ds2.initialSize=${initialSize} spring.shardingsphere.datasource.ds2.minIdle=${minIdle} spring.shardingsphere.datasource.ds2.maxActive=${maxActive} spring.shardingsphere.datasource.ds2.maxWait=${maxWait} spring.shardingsphere.datasource.ds2.validationQuery=SELECT 1 FROM DUAL spring.shardingsphere.datasource.ds2.timeBetweenEvictionRunsMillis=${timeBetweenEvictionRunsMillis} spring.shardingsphere.datasource.ds2.minEvictableIdleTimeMillis=${minEvictableIdleTimeMillis} # 分库配置 分片键按时间戳timestamp进行分片;具体的分片策略在指定的standardDatabaseShardingAlgorithm类中实现 spring.shardingsphere.rules.sharding.default-database-strategy.standard.sharding-column=timestamp spring.shardingsphere.rules.sharding.default-database-strategy.standard.sharding-algorithm-name=standardDatabaseShardingAlgorithm # 分表配置 shardingjdbc逻辑表thp_gps_data映射实际表thp_gps_data_$->{1..31};分片键按时间戳timestamp进行分片;具体的分片策略在指定的gpsStandardTableShardingAlgorithm类中实现 spring.shardingsphere.rules.sharding.tables.thp_gps_data.actual-data-nodes=ds$->{0..2}.thp_gps_data_$->{1..31} spring.shardingsphere.rules.sharding.tables.thp_gps_data.table-strategy.standard.sharding-column=timestamp spring.shardingsphere.rules.sharding.tables.thp_gps_data.table-strategy.standard.sharding-algorithm-name=gpsStandardTableShardingAlgorithm # 打印分库分表日志 spring.shardingsphere.props.sql-show=true
package com.zdft.bhdcm.config.shardingsphere; import groovy.util.logging.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue; import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue; import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.Collection; /** * <p> * 分库策略 * standard标准分片策略 * </p> * * @author lhy * @since 2023/2/2 */ @Slf4j @Component(value = "standardDatabaseShardingAlgorithm") public class StandardDatabaseShardingAlgorithm implements StandardShardingAlgorithm<Long> { //分库数量 private static int dataBaseSize; @Value("${dataBaseSize}") public void setDataBaseSize(int size) { dataBaseSize = size; } /** * 精确分片 * @param availableTargetNames 有效的数据源或表的名字。这里就对应配置文件中配置的数据源信息 * @param shardingValue 包含 逻辑表名、分片列和分片列的值。 * @return 返回目标结果 */ @Override public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingValue) { //分库的规则:时间戳取中间六位数对数据库数量取模 例:1688811155000 --> 811155 % 3 String timestamp = StringUtils.substring(String.valueOf(shardingValue.getValue()), -9, -3); Integer mod = Integer.parseInt(timestamp) % dataBaseSize; for (String targetName : availableTargetNames) { if (targetName.endsWith(String.valueOf(mod))) { return targetName; } } throw new UnsupportedOperationException(" route ds"+mod+" is not supported. please check your config"); } /** * 范围分片 * @param availableTargetNames * @param rangeShardingValue包含逻辑表名、分片列和分片列的条件范围。 * @return 返回目标结果。可以是多个。 */ @Override public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Long> rangeShardingValue) { //时间戳范围查询,设定所有库都查 Collection<String> collect = new ArrayList<>(); collect.add("ds0"); collect.add("ds1"); collect.add("ds2"); return collect; } @Override public void init() { } @Override public String getType() { return null; } }
package com.zdft.bhdcm.config.shardingsphere; import com.google.common.collect.Range; import com.zdft.bhdcm.utils.CommonUtils; import groovy.util.logging.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue; import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue; import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.Collection; import java.util.Date; /** * <p> * 分表策略 * standard标准分片策略 * </p> * * @author lhy * @since 2023/2/2 */ @Slf4j @Component(value = "gpsStandardTableShardingAlgorithm") public class GpsStandardTableShardingAlgorithm implements StandardShardingAlgorithm<Long> { //分表数量 private static int tableSize; @Value("${tableSize}") public void setTableSize(int size) { tableSize = size; } /** * 精确分片 * @param availableTargetNames 有效的数据源或表的名字。这里就对应配置文件中配置的数据源信息 * @param shardingValue 包含 逻辑表名、分片列和分片列的值。 * @return 返回目标结果 */ @Override public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingValue) { //分表的规则:从时间戳中获取天,哪一天落到哪个表 例:1688811155000 --> day(8)对应落到的库为thp_gps_data_8 //实现按照 = 或 IN 进行精确分片。 //例如 select * from t_order where order_id = 1 or order_id in (1,3,5) Long timestamp = Long.valueOf(Long.parseLong(String.valueOf(shardingValue.getValue()))); Integer day = CommonUtils.getDay(new Date(timestamp)); String key = shardingValue.getLogicTableName()+"_"+day; if(availableTargetNames.contains(key)){ return key; } throw new UnsupportedOperationException(" route "+key+" is not supported. please check your config"); } /** * 范围分片 * @param availableTargetNames * @param rangeShardingValue 包含逻辑表名、分片列和分片列的条件范围。 * @return 返回目标结果。可以是多个。 */ @Override public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Long> rangeShardingValue) { Collection<String> collect = new ArrayList<>(); //实现按照 Between 进行范围分片。 Range<Long> valueRange = rangeShardingValue.getValueRange();//获取查询条件中范围值 String slowerEndpoint = String.valueOf(valueRange.hasLowerBound()?valueRange.lowerEndpoint():"");//查询条件下限 String supperEndpoint = String.valueOf(valueRange.hasUpperBound()?valueRange.upperEndpoint():"");//查询条件上限 if(!slowerEndpoint.isEmpty()&&!supperEndpoint.isEmpty()) { Long lowerEndpoint = Math.abs(Long.parseLong(slowerEndpoint)); Long upperEndpoint = Math.abs(Long.parseLong(supperEndpoint)); Integer lowerMonth = CommonUtils.getMonth(new Date(lowerEndpoint)); Integer upperMonth = CommonUtils.getMonth(new Date(upperEndpoint)); if(lowerMonth.intValue()!=upperMonth.intValue()){ throw new BusinessException(ResultCodeEnum.SYS_ERROR.getCode(), ResultCodeEnum.SYS_ERROR.getDesc()+"时间范围不允许跨月,请核实!"); } Integer lowerDay = CommonUtils.getDay(new Date(lowerEndpoint)); Integer upperDay = CommonUtils.getDay(new Date(upperEndpoint)); for(;lowerDay <= upperDay; lowerDay ++){ collect.add(rangeShardingValue.getLogicTableName()+"_"+lowerDay); } } else if(!slowerEndpoint.isEmpty()&&supperEndpoint.isEmpty()) { Long lowerEndpoint = Math.abs(Long.parseLong(slowerEndpoint)); Integer lowerDay = CommonUtils.getDay(new Date(lowerEndpoint)); for (int day = lowerDay; day <= tableSize; day++) { collect.add(rangeShardingValue.getLogicTableName()+"_"+day); } }else if(slowerEndpoint.isEmpty()&&!supperEndpoint.isEmpty()) { Long upperEndpoint = Math.abs(Long.parseLong(supperEndpoint)); Integer upperDay = CommonUtils.getDay(new Date(upperEndpoint)); for (int day = 1; day <= upperDay; day++) { collect.add(rangeShardingValue.getLogicTableName()+"_"+day); } } return collect; } @Override public void init() { } @Override public String getType() { return null; } }
插入一条时间戳为1621571297000的数据
指定落到哪个库:
指定落到哪张表:
通过断点可以看到该条数据落到了ds1库中的thp_gps_data_21表中
查询一条时间戳为1621571297000的数据
指定查询哪个库:
指定查询哪张表:
ShardingSphere-JDBC打印的日志:
Logic SQL: SELECT t.id, t.equip_no, t.instruction_value, t.timestamp, FROM_UNIXTIME(SUBSTR(t.timestamp, 1, 10), '%Y-%m-%d %H:%i:%s') AS timestamp_date, t.longitude, t.latitude, t.altitude, t.speed, t.angle, t.signal_power, t.create_time, t.update_time FROM thp_gps_data t WHERE t.equip_no = ? AND t.timestamp = ?
SQLStatement: MySQLSelectStatement(table=Optional.empty, limit=Optional.empty, lock=Optional.empty, window=Optional.empty)
Actual SQL: ds1 ::: SELECT t.id, t.equip_no, t.instruction_value, t.timestamp, FROM_UNIXTIME(SUBSTR(t.timestamp, 1, 10), '%Y-%m-%d %H:%i:%s') AS timestamp_date, t.longitude, t.latitude, t.altitude, t.speed, t.angle, t.signal_power, t.create_time, t.update_time FROM thp_gps_data_21 t WHERE t.equip_no = ? AND t.timestamp = ? ::: [11111111111, 1621571297000]
Logic SQL:逻辑sql,这里的逻辑sql其实就是写在mapper.xml中的业务sql
Actual SQL:实际sql,ShardingSphere-JDBC实际执行的分库分表的sql
通过日志可以看到ShardingSphere-JDBC通过分片键timestamp直接帮我们查询了ds1库中的thp_gps_data_21表。
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。