Sharding-JDBC自定义复合分片算法
Sharding-JDBC自定义复合分片算法
一、背景
最近在看 Sharding-JDBC方面的内容,此处简单记录一下使用Sharding-JDBC中的复合分片键来实现分表的方法。
二、需求
假设我们有一张订单表customer_order,为了防止单表数据量太大,需要进行分表操作。
此处需要分为3个表 customer_order_0、customer_order_1和customer_order_2
1、对于客户端操作而言
1、同一个客户的订单,需要放到同一个表中。
2、根据订单号,需要知道这个订单在哪个中。
2、对于运营端操作而言
由于订单的数据量比较大,我们可以将一些需要作为搜索条件的数据保存到elasticsearch中,将订单的完整数据保存到hive中。Mysql数据库中的数据可以通过阿里开源的canal来同步到es中,这步操作略。
三、分片算法
由于同一个客户的订单分到同一个表,那么客户id(customerId)需要作为一个分片键。
由于需要根据订单id(orderId)确定到那一个表,所有客户id的分片信息需要糅合到订单id中,所以订单id也需要作为一个分片键。
因此在Sharding-JDBC中而言,这是一个复合分片算法。
1、客户id和订单id的生成规则
客户id: 使用雪花算法生成
订单id: 使用雪花算法生成 + 客户id的后2位
2、 确定数据落在那个表中
- 截取客户id后2位。
- 将后2位和3做取模操作,获取到表的后缀。
- 和3做取模操作,是因为需求中需要分为3个表。
 
- 将 customer_order_ 和上一步表的后缀拼接起来,就得到了一个真实表。
3、举例说明
1、客户id确定数据表
| 客户id | 截取后2位 | 和3做取模操作 | 确定表 | 
|---|---|---|---|
| 1397073528150429696 | 96 | 96 % 3 = 0 | customer_order_0 | 
| 1397073798557208576 | 76 | 76 % 3 = 1 | customer_order_1 | 
| 1397074377929003008 | 08 | 8 % 3 = 2 | customer_order_2 | 
2、订单id确定数据表
| 订单id | 截取后2位(等价于客户id的后2位) | 和3做取模操作 | 确定表 | 
|---|---|---|---|
| 139707353565823385696 | 96 | 96 % 3 = 0 | customer_order_0 | 
| 139707379855720857876 | 76 | 76 % 3 = 1 | customer_order_1 | 
| 139707437792900301008 | 08 | 8 % 3 = 2 | customer_order_2 | 
四、实现步骤
1、建表语句
create table customer_order_0
(
	id int auto_increment,
	order_id decimal(21) null,
	customer_id bigint null,
	saller_id bigint null,
	product_name varchar(300) null,
	constraint customer_order_pk
		primary key (id)
)
comment '优惠券订单' engine = innodb character set = utf8;
create table customer_order_1
(
	id int auto_increment,
	order_id decimal(21) null,
	customer_id bigint null,
	saller_id bigint null,
	product_name varchar(300) null,
	constraint customer_order_pk
		primary key (id)
)
comment '优惠券订单' engine = innodb character set = utf8;
comment '优惠券订单' engine = innodb character set = utf8;
create table customer_order_2
(
	id int auto_increment,
	order_id decimal(21) null,
	customer_id bigint null,
	saller_id bigint null,
	product_name varchar(300) null,
	constraint customer_order_pk
		primary key (id)
)
comment '优惠券订单' engine = innodb character set = utf8;
2、引入Sharding-JDBC的jar包
<dependency>
  <groupId>org.apache.shardingsphere</groupId>
  <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
  <version>4.1.1</version>
</dependency>
<!-- 为了生成id -->
<dependency>
  <groupId>cn.hutool</groupId>
  <artifactId>hutool-all</artifactId>
  <version>5.6.5</version>
</dependency>
3、编写分片算法
package com.huan.study.sharding.algorithm;
import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.complex.ComplexKeysShardingValue;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
 * 复合分片算法
 * 根据订单id(orderId)和客户id(customerId)后2位计算
 * 订单id 包含客户id 的后2位
 * 以客户id的后2位来确定是路由到那个表中
 * 1、目前处理 = 和 in 操作,其余的操作,比如 >、< 等不支持。
 *
 * @author huan.fu 2021/5/25 - 上午9:48
 */
public class OrderComplexKeysShardingAlgorithm implements ComplexKeysShardingAlgorithm<BigDecimal> {
    /**
     * 订单id列名
     */
    private static final String COLUMN_ORDER_ID = "order_id";
    /**
     * 客户id列名
     */
    private static final String COLUMN_CUSTOMER_ID = "customer_id";
    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, ComplexKeysShardingValue<BigDecimal> shardingValue) {
        if (!shardingValue.getColumnNameAndRangeValuesMap().isEmpty()) {
            throw new RuntimeException("不支持除了=和in的操作");
        }
        // 获取订单id
        Collection<BigDecimal> orderIds = shardingValue.getColumnNameAndShardingValuesMap().getOrDefault(COLUMN_ORDER_ID, new ArrayList<>(1));
        // 获取客户id
        Collection<BigDecimal> customerIds = shardingValue.getColumnNameAndShardingValuesMap().getOrDefault(COLUMN_CUSTOMER_ID, new ArrayList<>(1));
        // 整合订单id和客户id
        List<String> ids = new ArrayList<>(16);
        ids.addAll(ids2String(orderIds));
        ids.addAll(ids2String(customerIds));
        return ids.stream()
                // 截取 订单号或客户id的后2位
                .map(id -> id.substring(id.length() - 2))
                // 去重
                .distinct()
                // 转换成int
                .map(Integer::new)
                // 对可用的表名求余数,获取到真实的表的后缀
                .map(idSuffix -> idSuffix % availableTargetNames.size())
                // 转换成string
                .map(String::valueOf)
                // 获取到真实的表
                .map(tableSuffix -> availableTargetNames.stream().filter(targetName -> targetName.endsWith(tableSuffix)).findFirst().orElse(null))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
    }
    /**
     * 转换成String
     */
    private List<String> ids2String(Collection<?> ids) {
        List<String> result = new ArrayList<>(ids.size());
        ids.forEach(id -> result.add(Objects.toString(id)));
        return result;
    }
}
注意️:
1、此处为 订单id和客户id的复合分片算法。
2、由于订单id太长,所以使用了 BigDecimal类型。
3、订单id和客户id的后2位都可以确定数据最终是路由在哪张表中。
4、目前只实现了=和in的操作,不支持范围操作。
4、分表配置
# 启用 sharding-jdbc
spring.shardingsphere.enabled=true
# 配置数据源的名字
spring.shardingsphere.datasource.names=master
# 数据源配置
spring.shardingsphere.datasource.master.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.master.jdbc-url=jdbc:mysql://127.0.0.1:3306/temp_work?useUnicode=true&characterEncoding=utf8&autoReconnectForPools=true&useSSL=false
spring.shardingsphere.datasource.master.username=root
spring.shardingsphere.datasource.master.password=root
# 配置默认数据源为 master,即没有配置分表的数据,使用次数据源
spring.shardingsphere.sharding.default-data-source-name=master
# 数据库中实际的表
spring.shardingsphere.sharding.tables.customer_order.actual-data-nodes=master.customer_order_$->{0..2}
# 分片列
spring.shardingsphere.sharding.tables.customer_order.table-strategy.complex.sharding-columns=order_id,customer_id
# 分片算法
spring.shardingsphere.sharding.tables.customer_order.table-strategy.complex.algorithm-class-name=com.huan.study.sharding.algorithm.OrderComplexKeysShardingAlgorithm
# 显示sql
spring.shardingsphere.props.sql.show=true
spring.shardingsphere.sharding.tables.customer_order: 我们自己在程序中写sql时,订单表直接使用逻辑表customer_order即可,而不要使用真实的表,比如(customer_order_0等)。
spring.shardingsphere.sharding.tables.customer_order.table-strategy.complex.sharding-columns:指定需要分表的列。
spring.shardingsphere.sharding.tables.customer_order.table-strategy.complex.algorithm-class-name:指定复合分表算法类,指定的类需要有一个无参的构造方法。
5、mapper文件写法
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.huan.study.sharding.mappers.CustomerOrderMapper">
    <resultMap id="BaseResultMapper" type="com.huan.study.sharding.entity.CustomerOrder">
        <id column="id" property="id"/>
        <result column="order_id" property="orderId"/>
        <result column="customer_id" property="customerId"/>
        <result column="saller_id" property="sallerId"/>
        <result column="product_name" property="productName"/>
    </resultMap>
    <insert id="createOrder">
        insert into customer_order(order_id,customer_id,saller_id,product_name) values (#{orderId},#{customerId},#{sallerId},#{productName})
    </insert>
    <select id="findOrder" resultMap="BaseResultMapper">
        select * from customer_order where order_id = #{orderId}
    </select>
    <select id="findCustomerOrders" resultMap="BaseResultMapper">
        select * from customer_order where customer_id = #{customerId}
    </select>
</mapper>
需要注意,此处写的是逻辑表(customer_order),这个表在数据库中是不存在的,是在分表配置时指定的逻辑表。
五、完整代码
完整代码: https://gitee.com/huan1993/spring-cloud-parent/blob/master/sharding-jdbc/src/main/java/com/huan/study/sharding/algorithm/OrderComplexKeysShardingAlgorithm.java
git提交commitId: b14c1584b89991e909bd6852b1217872414d9db7
六、参考文档
Sharding-JDBC自定义复合分片算法的更多相关文章
- Sharding-Jdbc 自定义分库分表-复合分片算法自定义实现
		Sharding-JDBC中的分片策略有两个维度,分别是: 数据源分片策略(DatabaseShardingStrategy) 表分片策略(TableShardingStrategy) 其中,数据源分 ... 
- 浅谈sharding jdbc
		定位为轻量级Java框架,在Java的JDBC层提供的额外服务. 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架. ... 
- Sharding jdbc 强制路由策略(HintShardingStrategy)使用记录
		背景 随着项目运行时间逐渐增加,数据库中的数据也越来越多,虽然加索引,优化查询,但是数据量太大,还是会影响查询效率,也给数据库增加了负载. 再加上冷数据基本不使用的场景,决定采用分表来处理数据,从而来 ... 
- Sharding JDBC整合SpringBoot 2.x 和 MyBatis Plus 进行分库分表
		Sharding JDBC整合SpringBoot 2.x 和 MyBatis Plus 进行分库分表 交易所流水表的单表数据量已经过亿,选用Sharding-JDBC进行分库分表.MyBatis-P ... 
- sharding jdbc(sphere) 3.1.0 spring boot配置
		sharding jdbc 2.x系列详解参见https://www.cnblogs.com/zhjh256/p/9221634.html. 最近将sharding jdbc的配置从xml切换到了sp ... 
- 数据库中间件分片算法之enum
		前言 最近挺焦虑的,不知道未来该做什么,方向又是什么.只能用别慌,月亮也正在大海的某处迷茫.来安慰下自己.不过学习的初心咱们还是不要忘记.今天我们学习的是enum分片算法. 1.hash分区算法 2. ... 
- spring boot:配置shardingsphere(sharding jdbc)使用druid数据源(druid 1.1.23 / sharding-jdbc 4.1.1 / mybatis / spring boot 2.3.3)
		一,为什么要使用druid数据源? 1,druid的优点 Druid是阿里巴巴开发的号称为监控而生的数据库连接池 它的优点包括: 可以监控数据库访问性能 SQL执行日志 SQL防火墙 但spring ... 
- Spring boot项目集成Sharding Jdbc
		环境 jdk:1.8 framework: spring boot, sharding jdbc database: MySQL 搭建步骤 在pom 中加入sharding 依赖 <depend ... 
- 数据库中间件分片算法之stringhash
		前言 又是一个夜黑风高的晚上,带上无线耳机听一曲.突然很感慨一句话:生活就像心电图,一帆风顺就证明你挂了. 就如同我们干运维的,觉得很简单的事情,有时候能干出无限可能.还是言归正传吧,这一次我们来说说 ... 
随机推荐
- 斐波那契数(Java)
			斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 .该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和.也就是: F(0) = 0,F(1) = 1 F(n) = F(n ... 
- BeanFactory和ApplicationContext对比
			一.BeanFactory和ApplicationContext对比 其中,ApplicationContext容器即时加载,就是一加载配置文件,就会创建对象,且自动装配bean(即写道xml中bea ... 
- pycharm的常规使用
			1.修改当前项目的Py版本,是py2还是py3 pycharm-->settings-->选中要运行的项目-->选择py版本(如果你两个py版本都装在本机的话) 2.显示行数 在每行 ... 
- 洛谷P1803——凌乱的yyy(贪心)
			题目描述 现在各大oj上有n个比赛,每个比赛的开始.结束的时间点是知道的. yyy认为,参加越多的比赛,noip就能考的越好(假的) 所以,他想知道他最多能参加几个比赛. 由于yyy是蒟蒻,如果要参加 ... 
- DP之背包经典三例
			0/1背包 HDU2602 01背包(ZeroOnePack): 有N件物品和一个容量为V的背包,每种物品均只有一件.第i件物品的费用是c[i],价值是w[i].求解将哪些物品装入背包可使价值总和最大 ... 
- php发送邮件方法-亲测可用,email.class.php过期解决办法
			php虽然提供了mail()函数,但并不好用,而PHPMailer是一个不错的邮件发送工具,使用起来也是非常简单!使用PHPMailer发送邮件: <?php header("cont ... 
- Java基础系列(28)- 方法的定义和调用
			方法的定义 Java的方法类似于其他语言的函数,是一段用来完成特定功能的代码片段,一般情况下,定义一个方法包含以下语法: 修饰符 返回值类型 方法名(参数类型 参数名){ -- 方法体 -- retu ... 
- Digital Image Processing
			20190919 Review CCD:高端天文学,敏感度高,速度慢,成本高: CMOS:普遍使用,嵌入手机,速度快,有模式噪声(Pattern Noise,现在可以解决): 空间分辨率和时间分辨率: ... 
- 智汀家庭云-开发指南Golang:设备场景
			场景是指通过SA实现设备联动.例如,自动检测今天的天气情况,今天无雨,定时智能音箱播放浇花提醒,并且播报今天的天气情况. 根据自身需求,把多种控制并发的事情编辑成一个场景,并命名,可以通过场景控制很多 ... 
- CSS3思维导图
