温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

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

发布时间:2021-10-19 20:17:55 来源:亿速云 阅读:225 作者:柒染 栏目:大数据

本篇文章为大家展示了怎样进行水平分表实践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依赖

<!--  分库分表 -->
		<sharding-sphere.version>4.0.0-RC3-SNAPSHOT</sharding-sphere.version>
		
<!--  分库分表 4.0.0-RC3-SNAPSHOT -->
			<dependency>
			    <groupId>org.apache.shardingsphere</groupId>
			    <artifactId>sharding-jdbc-core</artifactId>
			    <version>${sharding-sphere.version}</version>
			</dependency>
			<!-- for spring boot springboot 配置-->
			<dependency>
			    <groupId>org.apache.shardingsphere</groupId>
			    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
			    <version>${sharding-sphere.version}</version>
			</dependency>
			
			<!-- for spring namespace  xml 配置-->
			<dependency>
			    <groupId>org.apache.shardingsphere</groupId>
			    <artifactId>sharding-jdbc-spring-namespace</artifactId>
			    <version>${sharding-sphere.version}</version>
			</dependency>
			
			<!--  transaction 分布式事务 -->
			<dependency>
			    <groupId>org.apache.shardingsphere</groupId>
			    <artifactId>sharding-transaction-xa-core</artifactId>
			    <version>${sharding-sphere.version}</version>
			</dependency>		
		

上面几个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
 */
@Slf4j
public class DatePreciseShardingAlgorithm implements PreciseShardingAlgorithm<Date> {
	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<String> availableTargetNames, PreciseShardingValue<Date> 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
 */
@Slf4j
public class DateRangeShardingAlgorithm implements RangeShardingAlgorithm<Date> {
	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<String> doSharding(Collection<String> availableTargetNames,
			RangeShardingValue<Date> shardingValue) {
		Collection<String> tableSet = Sets.newConcurrentHashSet();
		String logicTableName = shardingValue.getLogicTableName();
		Range<Date> 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

# 显示sql
spring.shardingsphere.props.sql.show=true

#imassunionpay数据源配置
spring.shardingsphere.datasource.imassunionpay.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.imassunionpay.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.imassunionpay.url=jdbc:mysql://***:3306/imass_union_pay?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.imassunionpay.username=root
spring.shardingsphere.datasource.imassunionpay.password=**


#范围水平分表
spring.shardingsphere.sharding.tables.imass_order_record.table-strategy.standard.sharding-column=create_time
spring.shardingsphere.sharding.tables.imass_order_record.table-strategy.standard.precise-algorithm-class-name=com.imassbank.unionpay.sharding.DatePreciseShardingAlgorithm
spring.shardingsphere.sharding.tables.imass_order_record.table-strategy.standard.range-algorithm-class-name=com.imassbank.unionpay.sharding.DateRangeShardingAlgorithm

# 分布式主键 内置的支持这三种 SNOWFLAKE/UUID/LEAF_SEGMENT
spring.shardingsphere.sharding.tables.imass_order_record.key-generator.column=order_record_id
spring.shardingsphere.sharding.tables.imass_order_record.key-generator.type=SNOWFLAKE

#druidDataSource
spring.shardingsphere.datasource.imassunionpay.initialSize=5
spring.shardingsphere.datasource.imassunionpay.minIdle=5
spring.shardingsphere.datasource.imassunionpay.maxActive=20
spring.shardingsphere.datasource.imassunionpay.maxWait=60000
spring.shardingsphere.datasource.imassunionpay.timeBetweenEvictionRunsMillis=60000
spring.shardingsphere.datasource.imassunionpay.minEvictableIdleTimeMillis=300000
spring.shardingsphere.datasource.imassunionpay.validationQuery=SELECT 1 FROM DUAL
spring.shardingsphere.datasource.imassunionpay.testWhileIdle=true
spring.shardingsphere.datasource.imassunionpay.testOnBorrow=false  
spring.shardingsphere.datasource.imassunionpay.testOnReturn=false
spring.shardingsphere.datasource.imassunionpay.poolPreparedStatements=true
spring.shardingsphere.datasource.imassunionpay.maxPoolPreparedStatementPerConnectionSize=20
spring.shardingsphere.datasource.imassunionpay.filters=stat,wall,cat

增删改查

插入很简单,只需要带上分表主键create_time即可

删改查

这三个操作都要带上分表主键create_time,举几个场景:

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

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

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

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

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI