文档章节

记一次水平分表实践(sharding-jdbc 4.0.0-RC3-SNAPSHOT)

liangxiao
 liangxiao
发布于 10/10 20:36
字数 1625
阅读 86
收藏 1

摘要

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

  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.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.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 = dates.lowerEndpoint();
		Date upperDate = dates.upperEndpoint();
		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));
			}
		}
		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支持那些操作,可以看看这篇博客。Sharding-Sphere数据分库分表实践(垂直分库分表)

© 著作权归作者所有

liangxiao
粉丝 22
博文 351
码字总数 137031
作品 0
杭州
后端工程师
私信 提问
【分库分表】sharding-jdbc + spring boot对大表进行分库分表

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kisscatforever/article/details/82746649 #一、前言 最近小编跳槽了,刚好入职了一家移动互联网公司。非常的...

AresCarry-王雷
2018/09/18
0
0
Sharding-JDBC 1.3.1 发布,当当 JDBC 增强驱动

Sharding-JDBC 1.3.1 发布了,Sharding-JDBC是当当应用框架ddframe中,从关系型数据库模块dd-rdb中分离出来的数据库水平分片框架,实现透明化数据库分库分表 访问。Sharding-JDBC是继dubbox和...

淡漠悠然
2016/06/28
3.1K
6
轻量级数据库中间件利器Sharding-JDBC深度解析

本文根据DBAplus社群第114期线上分享整理而成。 主题简介: 1、关系型数据库中间件核心功能介绍 2、Sharding-JDBC架构及内核解析 3、Sharding-JDBC未来展望 关系型数据库凭借灵活查询的SQL和...

张亮
2017/07/28
0
0
sharding-jdbc 1.0.0 发布,当当的 JDBC 增强驱动

sharding-jdbc 1.0 发布了,下载地址: https://github.com/dangdangdotcom/sharding-jdbc/releases/tag/1.0.0 Sharding-JDBC是当当应用框架ddframe中,从关系型数据库模块dd-rdb中分离出来的...

淡漠悠然
2016/02/02
5.8K
7
sharding-jdbc 1.1.0 正式发布,当当的 JDBC 增强驱动

sharding-jdbc 1.1.0 正式发布了。Sharding-JDBC是当当应用框架ddframe中,从关系型数据库模块dd-rdb中分离出来的数据库水平分片框架,实现透明化数据库分库分表 访问。Sharding-JDBC是继dub...

淡漠悠然
2016/03/28
1K
5

没有更多内容

加载失败,请刷新页面

加载更多

Andorid SQLite数据库开发基础教程(2)

Andorid SQLite数据库开发基础教程(2) 数据库生成方式 数据库的生成有两种方式,一种是使用数据库管理工具生成的数据库,我们将此类数据库称为预设数据库,另一种是使用代码生成的数据库。...

大学霸
22分钟前
3
0
YecPad 开源啦!: 基于C#的功能强大的可编辑记事本文本编辑软件

JY Lin 开源:YecPad : 基于C#的功能强大的可编辑记事本文本编辑软件 YecPad 是一款基于C#编程语言开发的功能强大的可编辑记事本文本编辑软件。 可以进行文本文件的打开、保存、删除及编辑功...

YDOOK
29分钟前
3
0
StringBuilder 与 StringBuffer 的区别

StringBuffer是线性安全的,支持并发操作,适合多线程。 StringBuilder线性不安全,不支持并发操作,适合单线程。 也就是说他们俩区别就在于支不支持并发操作,使用上基本上类似...

无名氏的程序员
32分钟前
3
0
js 找数组中的最值

本文转载于:专业的前端网站➸js 找数组中的最值 背景: 2个数组以下 , 比如 [[4, 9, 1, 3], [13, 35, 18, 26], [32, 35, 97, 39], [1000000, 1001, 857, 1]] 找最值的时候,我一开始想用两个...

前端老手
41分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部