关于后端:ShardingJDBC自定义复合分片算法

40次阅读

共计 6765 个字符,预计需要花费 17 分钟才能阅读完成。

一、背景

最近在看 Sharding-JDBC方面的内容,此处简略记录一下应用 Sharding-JDBC 中的 复合分片键 来实现分表的办法。

二、需要

假如咱们有一张订单表customer_order,为了避免单表数据量太大,须要进行分表操作。

此处须要分为 3 个表 customer_order_0customer_order_1customer_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、确定数据落在那个表中

  1. 截取客户 id 后 2 位。
  2. 将后 2 位和 3 做取模操作,获取到表的后缀。

    1. 和 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/sp…

git 提交 commitId:b14c1584b89991e909bd6852b1217872414d9db7

六、参考文档

1、https://shardingsphere.apache.org/document/legacy/4.x/document/cn/manual/sharding-jdbc/configuration/config-spring-boot/

正文完
 0