千家信息网

怎样进行水平分表实践sharding-jdbc 4.0.0-RC3-SNAPSHOT

发表于:2024-12-13 作者:千家信息网编辑
千家信息网最后更新 2024年12月13日,本篇文章为大家展示了怎样进行水平分表实践sharding-jdbc 4.0.0-RC3-SNAPSHOT,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。摘要本文
千家信息网最后更新 2024年12月13日怎样进行水平分表实践sharding-jdbc 4.0.0-RC3-SNAPSHOT

本篇文章为大家展示了怎样进行水平分表实践sharding-jdbc 4.0.0-RC3-SNAPSHOT,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。

摘要

本文示例是按月水平分表。存在一下两点不足:

  1. 分表主键没有设计好,本文用的是自增长id,没有把时间组合到主键中,导致少了一个只根据主键查询的场景;

  2. 表中没有冗余一个专门用来分表的字段,将分表字段跟业务字段耦合了,导致一些细节问题。比如,本文的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,举几个场景:

  1. 带了分表主键的。有的是直接带了分表主键的,比如刚插入的数据,接下来要一些更新,直接带上分表主键即可,但是更多的是时间范围查询,这种查询会用到范围查询策略。

  2. 根据业务主键去查(比较好的方法是在业务主键里面融入时间)

  3. 根据不带分表主键的业务数据查询。如果业务数据能关联到时间,则把这个时间(放大范围)当做分表主键去查。如果业务数据没有任何时间属性,则要集合业务特性做一些取舍,限定时间范围。举例如下:

    /**         * 只能查最近一个月的数据         */        @Override        public List queryOrderRecordByOrderId(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,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注行业资讯频道。

0