怎样进行水平分表实践sharding-jdbc 4.0.0-RC3-SNAPSHOT
本篇文章为大家展示了怎样进行水平分表实践sharding-jdbc 4.0.0-RC3-SNAPSHOT,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。
摘要
本文示例是按月水平分表。存在一下两点不足:
分表主键没有设计好,本文用的是自增长id,没有把时间组合到主键中,导致少了一个只根据主键查询的场景;
表中没有冗余一个专门用来分表的字段,将分表字段跟业务字段耦合了,导致一些细节问题。比如,本文的create_time 是带毫秒的,一些时间加减操作会丢失毫秒 导致查不到数据。
限于团队规模,没有做读写分离。
实践
背景
目前我们支付订单中心流水表有2400w数据(mysql单表),查询速度非常慢,且以每天20w+的速度在增长。考虑到这个数据量(每个月600w数据),我们打算按月分表,这样每张表600w+数据量,比较适合查询。
设计思路
将2019年11月份之前的数据都存放在默认的表中(imass_order_record),这样做有一个好处,就是不用迁移任何历史数据。在这之后的数据,按月建表。比如2019年11月11号的数据进imass_order_record_201911这张表,2019年12月11号的数据写进imass_order_record_201912这张表。
这里在做数据查询的时候稍微注意"月切"问题。
分表策略
jar依赖
4.0.0-RC3-SNAPSHOT org.apache.shardingsphere sharding-jdbc-core ${sharding-sphere.version} org.apache.shardingsphere sharding-jdbc-spring-boot-starter ${sharding-sphere.version} org.apache.shardingsphere sharding-jdbc-spring-namespace ${sharding-sphere.version} org.apache.shardingsphere sharding-transaction-xa-core ${sharding-sphere.version}
上面几个jar 根据需要添加。
准确分表策略
package com.imassbank.unionpay.sharding;import java.text.ParseException;import java.time.LocalDate;import java.time.ZoneId;import java.time.format.DateTimeFormatter;import java.util.Collection;import java.util.Date;import java.util.Locale;import org.apache.commons.lang3.time.DateUtils;import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;import lombok.extern.slf4j.Slf4j;/** * @author Michael Feng * @date 2019年9月19日 * @description */@Slf4jpublic class DatePreciseShardingAlgorithm implements PreciseShardingAlgorithm{ private static DateTimeFormatter sdf = DateTimeFormatter.ofPattern("yyyyMM", Locale.CHINA); private static final String SEPERATOR = "_";//表名分隔符 private static Date lowwerDate = null; static { try { lowwerDate = DateUtils.parseDate("201911", "yyyyMM"); } catch (ParseException e) { log.error("解析其实日期异常",e); } } @Override public String doSharding(Collection availableTargetNames, PreciseShardingValue shardingValue) { String loginTableName = shardingValue.getLogicTableName(); Date createTime = shardingValue.getValue(); if(createTime == null || createTime.before(lowwerDate) ){ log.info("创建时间为空,或者当前时间:{} 小于 2019-11 ,进入默认表",createTime); return loginTableName; } String yyyyMM = ""; try{ yyyyMM =SEPERATOR+ createTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().format(sdf); log.info("进入表:{}",loginTableName+yyyyMM); return loginTableName+yyyyMM; }catch(Exception e){ log.error("解析创建时间异常,分表失败,进入默认表",e); } return loginTableName; }}
范围查询策略
package com.imassbank.unionpay.sharding;import java.text.ParseException;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.ZoneId;import java.time.format.DateTimeFormatter;import java.util.Calendar;import java.util.Collection;import java.util.Date;import java.util.Locale;import java.util.concurrent.atomic.AtomicInteger;import org.apache.commons.lang3.time.DateUtils;import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;import com.alibaba.fastjson.JSONObject;import com.google.common.collect.Range;import com.google.common.collect.Sets;import lombok.extern.slf4j.Slf4j;/** * @author Michael Feng * @date 2019年9月19日 * @description */@Slf4jpublic class DateRangeShardingAlgorithm implements RangeShardingAlgorithm{ private static DateTimeFormatter sdf = DateTimeFormatter.ofPattern("yyyyMM", Locale.CHINA); private static final String SEPERATOR = "_";//表名分隔符 private static Date lowwerDate = null; static { try { lowwerDate = DateUtils.parseDate("201911", "yyyyMM"); } catch (ParseException e) { log.error("解析其实日期异常",e); } } @Override public Collection doSharding(Collection availableTargetNames, RangeShardingValue shardingValue) { Collection tableSet = Sets.newConcurrentHashSet(); String logicTableName = shardingValue.getLogicTableName(); Range dates = shardingValue.getValueRange(); Date lowDate = DateUtils.truncate( dates.lowerEndpoint(),Calendar.MONTH ); Date upperDate = DateUtils.truncate(dates.upperEndpoint(),Calendar.MONTH) ;//为了把当前月份加进来 AtomicInteger i = new AtomicInteger(0); while(DateUtils.addMonths(lowDate, i.get()).compareTo(upperDate)<=0){ Date date = DateUtils.addMonths(lowDate, i.getAndAdd(1)); if(date.before(lowwerDate)){//早于其实日期的,都从默认的表里面找 tableSet.add(logicTableName); }else{ tableSet.add(logicTableName+SEPERATOR+date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().format(sdf)); } } log.info("要查询的表集合:{}",JSONObject.toJSONString(tableSet)); return tableSet; }}
分表配置
#数据源spring.shardingsphere.datasource.names=imassunionpay#默认数据源spring.shardingsphere.sharding.default-data-source-name=imassunionpay# 显示sqlspring.shardingsphere.props.sql.show=true#imassunionpay数据源配置spring.shardingsphere.datasource.imassunionpay.type=com.alibaba.druid.pool.DruidDataSourcespring.shardingsphere.datasource.imassunionpay.driver-class-name=com.mysql.cj.jdbc.Driverspring.shardingsphere.datasource.imassunionpay.url=jdbc:mysql://***:3306/imass_union_pay?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&serverTimezone=Asia/Shanghaispring.shardingsphere.datasource.imassunionpay.username=rootspring.shardingsphere.datasource.imassunionpay.password=**#范围水平分表spring.shardingsphere.sharding.tables.imass_order_record.table-strategy.standard.sharding-column=create_timespring.shardingsphere.sharding.tables.imass_order_record.table-strategy.standard.precise-algorithm-class-name=com.imassbank.unionpay.sharding.DatePreciseShardingAlgorithmspring.shardingsphere.sharding.tables.imass_order_record.table-strategy.standard.range-algorithm-class-name=com.imassbank.unionpay.sharding.DateRangeShardingAlgorithm# 分布式主键 内置的支持这三种 SNOWFLAKE/UUID/LEAF_SEGMENTspring.shardingsphere.sharding.tables.imass_order_record.key-generator.column=order_record_idspring.shardingsphere.sharding.tables.imass_order_record.key-generator.type=SNOWFLAKE#druidDataSourcespring.shardingsphere.datasource.imassunionpay.initialSize=5spring.shardingsphere.datasource.imassunionpay.minIdle=5spring.shardingsphere.datasource.imassunionpay.maxActive=20spring.shardingsphere.datasource.imassunionpay.maxWait=60000spring.shardingsphere.datasource.imassunionpay.timeBetweenEvictionRunsMillis=60000spring.shardingsphere.datasource.imassunionpay.minEvictableIdleTimeMillis=300000spring.shardingsphere.datasource.imassunionpay.validationQuery=SELECT 1 FROM DUALspring.shardingsphere.datasource.imassunionpay.testWhileIdle=truespring.shardingsphere.datasource.imassunionpay.testOnBorrow=false spring.shardingsphere.datasource.imassunionpay.testOnReturn=falsespring.shardingsphere.datasource.imassunionpay.poolPreparedStatements=truespring.shardingsphere.datasource.imassunionpay.maxPoolPreparedStatementPerConnectionSize=20spring.shardingsphere.datasource.imassunionpay.filters=stat,wall,cat
增删改查
增
插入很简单,只需要带上分表主键create_time即可
删改查
这三个操作都要带上分表主键create_time,举几个场景:
带了分表主键的。有的是直接带了分表主键的,比如刚插入的数据,接下来要一些更新,直接带上分表主键即可,但是更多的是时间范围查询,这种查询会用到范围查询策略。
根据业务主键去查(比较好的方法是在业务主键里面融入时间)
根据不带分表主键的业务数据查询。如果业务数据能关联到时间,则把这个时间(放大范围)当做分表主键去查。如果业务数据没有任何时间属性,则要集合业务特性做一些取舍,限定时间范围。举例如下:
/** * 只能查最近一个月的数据 */ @Override public ListqueryOrderRecordByOrderId(String orderId) { if(StringUtils.isEmpty(orderId)){ logger.info("支付订单号为空"); return null; } Date endCreateTime = new Date(); Date startCreateTime = DateUtils.truncate(DateUtils.addMonths(endCreateTime, -1),Calendar.DAY_OF_MONTH); List recordList = orderRecordExtendMapper.queryOrderRecordByOrderId(orderId,startCreateTime,endCreateTime); SensitiveProcessor.decryptList(recordList); return recordList; }
这里可以根据业务场景做更大时间跨度的查询。
一般业务量大的时候,会做一个读写分离。数据写入到分库分表的数据库,做持久化。同事将需要查询的数据往es这种搜索引擎写一份,这样在搜索引擎里面可以随便查。
踩过的坑
Cannot support multiple schemas in one SQL
这个问题sharding-jdbc官方说过,不支持多schema。看了一下源码,是在解析sql的表的时候,比较了各个表的schema,不同则抛出这个异常。实际上,查询语句跟分表毫无关系的话,应该是可以支持这种多schema的。后期对源码理解更深入的时候,看看能不能参考强制路由的思路,允许应用选择是否做sql解析。
范围查询sql必须是between and,不能 create_time > * and create_time <
这种语句不会调用到范围查询策略。
分布式主键,用的是snowflake,两台实例,没有配置workId,导致分布式主键重复。
一种是设置workId,我是直接改写了一下源码,根据ip来设置workId。
还有一些其它的坑,有点忘了。
上述内容就是怎样进行水平分表实践sharding-jdbc 4.0.0-RC3-SNAPSHOT,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注行业资讯频道。