美文网首页
Sharding-JDBC自定义复合分片算法

Sharding-JDBC自定义复合分片算法

作者: huan1993 | 来源:发表于2021-05-25 16:23 被阅读0次

    一、背景

    最近在看 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/spring-cloud-parent/blob/master/sharding-jdbc/src/main/java/com/huan/study/sharding/algorithm/OrderComplexKeysShardingAlgorithm.java

    git提交commitId: b14c1584b89991e909bd6852b1217872414d9db7

    六、参考文档

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

    相关文章

      网友评论

          本文标题:Sharding-JDBC自定义复合分片算法

          本文链接:https://www.haomeiwen.com/subject/zrkcsltx.html