怎样进行水平分表实践sharding-jdbc4.0.0-RC3-SNAPSHOT

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

创新互联是一家网站设计公司,集创意、互联网应用、软件技术为一体的创意网站建设服务商,主营产品:成都响应式网站建设品牌网站制作营销型网站建设。我们专注企业品牌在网站中的整体树立,网络互动的体验,以及在手机等移动端的优质呈现。成都做网站、网站设计、外贸营销网站建设、移动互联产品、网络运营、VI设计、云产品.运维为核心业务。为用户提供一站式解决方案,我们深知市场的竞争激烈,认真对待每位客户,为客户提供赏析悦目的作品,网站的价值服务。

摘要

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

  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
 */
@Slf4j
public 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
 */
@Slf4j
public 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

# 显示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 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,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注创新互联行业资讯频道。


名称栏目:怎样进行水平分表实践sharding-jdbc4.0.0-RC3-SNAPSHOT
网页URL:http://myzitong.com/article/gcoohc.html