Sharding-Jdbc 自定义复合分片的实现(分库分表)

目录
  • Sharding-JDBC的数据分片策略
    • 分片键
    • 分片算法
    • 分片策略
    • SQL Hint
  • 实战–自定义复合分片策略
  • 小结

Sharding-JDBC中的分片策略有两个维度,分别是:

  • 数据源分片策略(DatabaseShardingStrategy)
  • 表分片策略(TableShardingStrategy)

其中,数据源分片策略表示:数据路由到的物理目标数据源,表分片策略表示数据被路由到的目标表。

特别的,表分片策略是依赖于数据源分片策略的,也就是说要先分库再分表,当然也可以只分表。

Sharding-JDBC的数据分片策略

Sharding-JDBC的分片策略包含了分片键和分片算法。由于分片算法与业务实现紧密相关,因此Sharding-JDBC没有提供内置的分片算法,而是通过分片策略将各种场景提炼出来,提供了高层级的抽象,通过提供接口让开发者自行实现分片算法。

以下内容引用自官方文档。官方文档

首先介绍四种分片算法。

通过分片算法将数据分片,支持通过=、BETWEEN和IN分片。
分片算法需要应用方开发者自行实现,可实现的灵活度非常高。

目前提供4种分片算法。由于分片算法和业务实现紧密相关,
因此并未提供内置分片算法,而是通过分片策略将各种场景提炼出来,
提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。

分片键

用于分片的数据库字段,是将数据库(表)水平拆分的关键字段。例:将订单表中的订单主键的尾数取模分片,则订单主键为分片字段。 SQL中如果无分片字段,将执行全路由,性能较差。 除了对单分片字段的支持,ShardingSphere也支持根据多个字段进行分片。

分片算法

通过分片算法将数据分片,支持通过=BETWEENIN分片。分片算法需要应用方开发者自行实现,可实现的灵活度非常高。

目前提供4种分片算法。由于分片算法和业务实现紧密相关,因此并未提供内置分片算法,而是通过分片策略将各种场景提炼出来,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。

精确分片算法

对应PreciseShardingAlgorithm,用于处理使用单一键作为分片键的=与IN进行分片的场景。需要配合StandardShardingStrategy使用。

范围分片算法

对应RangeShardingAlgorithm,用于处理使用单一键作为分片键的BETWEEN AND进行分片的场景。需要配合StandardShardingStrategy使用。

复合分片算法

对应ComplexKeysShardingAlgorithm,用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合ComplexShardingStrategy使用。

Hint分片算法

对应HintShardingAlgorithm,用于处理使用Hint行分片的场景。需要配合HintShardingStrategy使用。

分片策略

包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。目前提供5种分片策略。

标准分片策略

对应StandardShardingStrategy。提供对SQL语句中的=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持单分片键,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm是必选的,用于处理=和IN的分片。RangeShardingAlgorithm是可选的,用于处理BETWEEN AND分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理。

复合分片策略

对应ComplexShardingStrategy。复合分片策略。提供对SQL语句中的=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。

行表达式分片策略

对应InlineShardingStrategy。使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如:t_user_$->{u_id % 8}表示t_user表根据u_id模8,而分成8张表,表名称为t_user_0t_user_7

Hint分片策略

对应HintShardingStrategy。通过Hint而非SQL解析的方式分片的策略。

不分片策略

对应NoneShardingStrategy。不分片的策略。

SQL Hint

对于分片字段非SQL决定,而由其他外置条件决定的场景,可使用SQL Hint灵活的注入分片字段。例:内部系统,按照员工登录主键分库,而数据库中并无此字段。SQL Hint支持通过Java API和SQL注释(待实现)两种方式使用。

实战–自定义复合分片策略

由于目的为贴近实战,因此着重讲解如何实现复杂分片策略,即实现ComplexShardingStrategy接口定制生产可用的分片策略。

AdminIdShardingAlgorithm 复合分片算法代码如下:

import com.google.common.collect.Range;
import io.shardingjdbc.core.api.algorithm.sharding.ListShardingValue;
import io.shardingjdbc.core.api.algorithm.sharding.PreciseShardingValue;
import io.shardingjdbc.core.api.algorithm.sharding.RangeShardingValue;
import io.shardingjdbc.core.api.algorithm.sharding.ShardingValue;
import io.shardingjdbc.core.api.algorithm.sharding.complex.ComplexKeysShardingAlgorithm;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import java.util.*;

/**
 */
public class AdminIdShardingAlgorithm implements ComplexKeysShardingAlgorithm {

    private Logger logger = Logger.getLogger(getClass());

    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, Collection<ShardingValue> shardingValues) {
        Collection<String> routTables = new HashSet<String>();
        if (shardingValues != null) {
            for (ShardingValue shardingValue : shardingValues) {

                // eq in 条件
                if (shardingValue instanceof ListShardingValue) {
                    ListShardingValue listShardingValue = (ListShardingValue) shardingValue;
                    Collection<Comparable> values = listShardingValue.getValues();
                    if (values != null) {
                        Iterator<Comparable> it = values.iterator();
                        while (it.hasNext()) {
                            Comparable value = it.next();
                            String routTable = getRoutTable(shardingValue.getLogicTableName(), value);
                            if (StringUtils.isNotBlank(routTable)) {
                                routTables.add(routTable);
                            }
                        }
                    }

                    // eq 条件
                } else if (shardingValue instanceof PreciseShardingValue) {
                    PreciseShardingValue preciseShardingValue = (PreciseShardingValue) shardingValue;

                    Comparable value = preciseShardingValue.getValue();
                    String routTable = getRoutTable(shardingValue.getLogicTableName(), value);
                    if (StringUtils.isNotBlank(routTable)) {
                        routTables.add(routTable);
                    }
                    // between 条件
                } else if (shardingValue instanceof RangeShardingValue) {
                    RangeShardingValue rangeShardingValue = (RangeShardingValue) shardingValue;
                    Range<Comparable> valueRange = rangeShardingValue.getValueRange();
                    Comparable lowerEnd = valueRange.lowerEndpoint();
                    Comparable upperEnd = valueRange.upperEndpoint();

                    Collection<String> tables = getRoutTables(shardingValue.getLogicTableName(), lowerEnd, upperEnd);
                    if (tables != null && tables.size() > 0) {
                        routTables.addAll(tables);
                    }
                }

                if (routTables != null && routTables.size() > 0) {
                    return routTables;
                }
            }
        }

        throw new UnsupportedOperationException();

    }

    private String getRoutTable(String logicTable, Comparable keyValue) {
        Map<String, List<KeyShardingRange>> keyRangeMap = KeyShardingRangeConfig.getKeyRangeMap();

        List<KeyShardingRange> keyShardingRanges = keyRangeMap.get(KeyShardingRangeConfig.SHARDING_ID_KEY);

        if (keyValue != null && keyShardingRanges != null) {
            if (keyValue instanceof Integer) {
                keyValue = Long.valueOf(((Integer) keyValue).intValue());
            }
            for (KeyShardingRange range : keyShardingRanges) {
                if (keyValue.compareTo(range.getMin()) >= 0 && keyValue.compareTo(range.getMax()) <= 0) {
                    return logicTable + range.getTableKey();
                }
            }
        }
        return null;
    }
    private Collection<String> getRoutTables(String logicTable, Comparable lowerEnd, Comparable upperEnd) {
        Map<String, List<KeyShardingRange>> keyRangeMap = KeyShardingRangeConfig.getKeyRangeMap();

        List<KeyShardingRange> keyShardingRanges = keyRangeMap.get(KeyShardingRangeConfig.SHARDING_CONTENT_ID_KEY);
        Set<String> routTables = new HashSet<String>();
        if (lowerEnd != null && upperEnd != null && keyShardingRanges != null) {
            if (lowerEnd instanceof Integer) {
                lowerEnd = Long.valueOf(((Integer) lowerEnd).intValue());
            }

            if (upperEnd instanceof Integer) {
                upperEnd = Long.valueOf(((Integer) upperEnd).intValue());
            }
            boolean start = false;
            for (KeyShardingRange range : keyShardingRanges) {
                if (lowerEnd.compareTo(range.getMin()) >= 0 && lowerEnd.compareTo(range.getMax()) <= 0) {
                    start = true;
                }
                if (start) {
                    routTables.add(logicTable + range.getTableKey());
                }
                if (upperEnd.compareTo(range.getMin()) >= 0 && upperEnd.compareTo(range.getMax()) <= 0) {
                    break;
                }
            }
        }
        return routTables;
    }
}

范围 map 如下:

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 分片键分布配置
 */
public class KeyShardingRangeConfig {

    private static Map<String, List<KeyShardingRange>> keyRangeMap = new LinkedHashMap<String, List<KeyShardingRange>>();

    public static final String SHARDING_ORDER_ID_KEY = "id";

    public static final String SHARDING_USER_ID_KEY = "adminId";

    public static final String SHARDING_DATE_KEY = "createTime";

    static {
        List<KeyShardingRange> idRanges = new ArrayList<KeyShardingRange>();
        idRanges.add(new KeyShardingRange(0, "_0", 0L, 4000000L));
        idRanges.add(new KeyShardingRange(1, "_1", 4000001L, 8000000L));
        idRanges.add(new KeyShardingRange(2, "_2", 8000001L, 12000000L));
        idRanges.add(new KeyShardingRange(3, "_3", 12000001L, 16000000L));
        idRanges.add(new KeyShardingRange(4, "_4", 16000001L, 2000000L));
        keyRangeMap.put(SHARDING_ID_KEY, idRanges);

        List<KeyShardingRange> contentIdRanges = new ArrayList<KeyShardingRange>();
        contentIdRanges.add(new KeyShardingRange(0, "_0", 0L, 4000000L));
        contentIdRanges.add(new KeyShardingRange(1, "_1", 4000001L, 8000000L));
        contentIdRanges.add(new KeyShardingRange(2, "_2", 8000001L, 12000000L));
        contentIdRanges.add(new KeyShardingRange(3, "_3", 12000001L, 16000000L));
        contentIdRanges.add(new KeyShardingRange(4, "_4", 16000001L, 2000000L));
        keyRangeMap.put(SHARDING_CONTENT_ID_KEY, contentIdRanges);

        List<KeyShardingRange> timeRanges = new ArrayList<KeyShardingRange>();
        timeRanges.add(new KeyShardingRange("_0", 20170701L, 20171231L));
        timeRanges.add(new KeyShardingRange("_1", 20180101L, 20180630L));
        timeRanges.add(new KeyShardingRange("_2", 20180701L, 20181231L));
        timeRanges.add(new KeyShardingRange("_3", 20190101L, 20190630L));
        timeRanges.add(new KeyShardingRange("_4", 20190701L, 20191231L));
        keyRangeMap.put(SHARDING_DATE_KEY, timeRanges);
    }

    public static Map<String, List<KeyShardingRange>> getKeyRangeMap() {
        return keyRangeMap;
    }
}

核心逻辑解析

梳理一下逻辑,首先介绍一下该方法的入参

参数名                                         解释

availableTargetNames     有效的物理数据源,即配置文件中的 t_order_0,t_order_1,t_order_2,t_order_3

shardingValues             分片属性,如:{“columnName”:”order_id”,”logicTableName”:”t_order”,”values”:[“UD020003011903261545436593200002”]} ,包含:分片列名,逻辑表名,当前列的具体分片值

该方法返回值为

参数名                                                 解释

Collection<String>      分片结果,可以是目标数据源,也可以是目标数据表,此处为数据源

接着回来看业务逻辑,伪代码如下

首先打印了一下数据源集合 availableTargetNames 以及 分片属性 shardingValues的值,执行测试用例后,日志输出为:

availableTargetNames:["t_order_0","t_order_1","t_order_2","t_order_3"],
shardingValues:[{"columnName":"user_id","logicTableName":"t_order","values":["UD020003011903261545436593200002"]},
                {"columnName":"order_id","logicTableName":"t_order","values":["OD000000011903261545475143200001"]}]

从日志可以看出,我们可以在该路由方法中取到配置时的物理数据源列表,以及在运行时获取本次执行时的路由属性及其值

完整的逻辑流程如下:

  • 定义一个集合用于放置最终匹配好的路由数据源,接着对shardingValues进行遍历,目的为至少命中一个路由键
  • 遍历shardingValues循环体中,打印了当前循环的shardingValue,即实际的分片键的数值,如:订单号、用户id等。通过getIndex方法,获取该分片键值中包含的物理数据源索引
  • 接着遍历数据源列表availableTargetNames,截取当前循环对应availableTargetName的索引值,(eg: ds0则取0,ds1则取1…以此类推)将该配置的物理数据源索引与 第2步 中解析到的数据源路由索引进行比较,两者相等则表名我们期望将该数据路由到该匹配到的数据源。
  • 执行这个过程,直到匹配到一个路由键则停止循环,之所以这么做是因为我们是复合分片,至少要匹配到一个路由规则,才能停止循环,最终将路由到的物理数据源(ds0/ds1/ds2/ds3)通过add方法添加到事先定义好的集合中并返回给框架。
  • 逻辑结束。

小结

本文中,基本完成了Sharding-JDBC中复合分片路由算法的自定义实现,并经过测试验证符合预期,该实现方案在生产上已经经历过考验。定义分片路由策略的核心还是要熟悉ComplexKeysShardingAlgorithm,对如何解析 doSharding(CollectionavailableTargetNames, CollectionshardingValues)的参数有明确的认识,最简单的方法就是实际打印一下参数,相信会让你更加直观的感受到作者优良的接口设计能力,站在巨人的肩膀上我们能看到更远。

到此这篇关于Sharding-Jdbc 自定义复合分片的实现的文章就介绍到这了,更多相关Sharding-Jdbc 自定义复合分片内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Spring Boot 集成 Sharding-JDBC + Mybatis-Plus 实现分库分表功能

    一. Sharding-jdbc简介 " Sharding-jdbc是开源的数据库操作中间件:定位为轻量级Java框架,在Java的JDBC层提供的额外服务.它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架. 官方文档地址:https://shardingsphere.apache.org/document/current/cn/overview/ 本文demo实现了分库分表功能.如有错误,欢迎各位在评论中指出.不

  • SpringBoot整合Sharding-JDBC实现MySQL8读写分离

    目录 一.前言 二.项目目录结构 三.pom文件 四.配置文件(基于YAML)及SQL建表语句 五.Mapper.xml文件及Mapper接口 六 .Controller及Mocel文件 七.结果 八.Sharding-JDBC不同版本上的配置 一.前言 这是一个基于SpringBoot整合Sharding-JDBC实现读写分离的极简教程,笔者使用到的技术及版本如下: SpringBoot 2.5.2 MyBatis-Plus 3.4.3 Sharding-JDBC 4.1.1 MySQL8集群

  • 利用Sharding-Jdbc组件实现分表

    看到了当当开源的Sharding-JDBC组件,它可以在几乎不修改代码的情况下完成分库分表的实现.摘抄其中一段介绍: Sharding-JDBC直接封装JDBC API,可以理解为增强版的JDBC驱动,旧代码迁移成本几乎为零: 可适用于任何基于java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC. 可基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid等. 理论上可支持任意实现JDB

  • SpringBoot 2.0 整合sharding-jdbc中间件实现数据分库分表

    一.水平分割 1.水平分库 1).概念:  以字段为依据,按照一定策略,将一个库中的数据拆分到多个库中. 2).结果  每个库的结构都一样:数据都不一样:  所有库的并集是全量数据: 2.水平分表 1).概念  以字段为依据,按照一定策略,将一个表中的数据拆分到多个表中. 2).结果  每个表的结构都一样:数据都不一样:  所有表的并集是全量数据: 二.Shard-jdbc 中间件 1.架构图 2.特点 1).Sharding-JDBC直接封装JDBC API,旧代码迁移成本几乎为零. 2).适

  • 详解Spring Boot中整合Sharding-JDBC读写分离示例

    在我<Spring Cloud微服务-全栈技术与案例解析>书中,第18章节分库分表解决方案里有对Sharding-JDBC的使用进行详细的讲解. 之前是通过XML方式来配置数据源,读写分离策略,分库分表策略等,之前有朋友也问过我,有没有Spring Boot的方式来配置,既然已经用Spring Boot还用XML来配置感觉有点不协调. 其实吧我个人觉得只要能用,方便看,看的懂就行了,mybatis的SQL不也是写在XML中嘛. 今天就给大家介绍下Spring Boot方式的使用,主要讲解读写分

  • Sharding-Jdbc 自定义复合分片的实现(分库分表)

    目录 Sharding-JDBC的数据分片策略 分片键 分片算法 分片策略 SQL Hint 实战–自定义复合分片策略 小结 Sharding-JDBC中的分片策略有两个维度,分别是: 数据源分片策略(DatabaseShardingStrategy) 表分片策略(TableShardingStrategy) 其中,数据源分片策略表示:数据路由到的物理目标数据源,表分片策略表示数据被路由到的目标表. 特别的,表分片策略是依赖于数据源分片策略的,也就是说要先分库再分表,当然也可以只分表. Shar

  • SpringBoot集成Sharding Jdbc使用复合分片的实践

    目录 1.Sharing JDBC 简介 2.系统改造 2.1 对接外部系统的系统 2.2 内部系统间的调用 3.解决方案 4.代码实现 4.1 Sharding JDBC 配置 4.2 数据源操作类 4.3 分片测试类 4.4 测试结果 参考文章: 最近主要的工作重心是数据库的容量规划. 随着业务的逐渐增大,原有保存在单表的数据量也日益增强.数据库数据会随着业务的发展而不断增多,因此数据操作,如增删改查的开销也会越来越大.再加上物理服务器的资源有限(CPU.磁盘.内存.IO 等).最终数据库所

  • SpringBoot 如何使用sharding jdbc进行分库分表

    目录 基于4.0版本,Springboot2.1 在pom里确保有如下引用 里面我profiles.active了另一个 之后手工把表都建好 写个测试代码 需要注意一个坑 基于4.0版本,Springboot2.1 之前写过一篇使用sharding-jdbc进行分库分表的文章,不过当时的版本还比较早,现在已经不能用了.这一篇是基于最新版来写的. 新版已经变成了shardingsphere了,https://shardingsphere.apache.org/. 有点不同的是,这一篇,我们是采用多

  • SpringBoot整合sharding-jdbc实现自定义分库分表的实践

    目录 一.前言 二.简介 1.分片键 2.分片算法 三.程序实现 一.前言 SpringBoot整合sharding-jdbc实现分库分表与读写分离 本文将通过自定义算法来实现定制化的分库分表来扩展相应业务 二.简介 1.分片键 用于数据库/表拆分的关键字段 ex: 用户表根据user_id取模拆分到不同的数据库中 2.分片算法 可参考:https://shardingsphere.apache.org/document/current/cn/user-manual/shardingsphere

  • 详解分库分表后非分片键如何查询

    目录 正文 设计一:冗余法 方法二:索引表法 方法三:基因法 小结 正文 我们知道在分库分表中对于toC业务来说,需要选择用户属性如 user_id 作为分片键,不推荐使用order_id这样的作为分片键. 那问题来了,对于订单表来说,选择了user_id作为分片键以后如何查看订单详情呢?比如下面这样一条SQL: SELECT * FROM T_ORDER WHERE order_id = 801462878019256325 由于查询条件中的order_id不是分片键,所以需要查询所有分片才能

  • springboot整合shardingjdbc实现分库分表最简单demo

    一.概览 1.1 简介 ShardingSphere-JDBC定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务. 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架. 适用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC. 支持任何第三方的数据库连接池,如:DBCP,

  • MySQL分库分表与分区的入门指南

    前言 关系型数据库比较容易成为系统瓶颈,单机存储容量.连接数.处理能力都有限,当数据量和并发量起来之后,就必须对数据库进行切分了. 数据切分(sharding)的手段就是分库分表.分库分表有两方面,可能是光分库不分表,也可能是光分表不分库. 数据库分布式的核心内容无非就是数据切分,以及切分后对数据的定位.整合. 为什么要分库分表 分表 单表数据量太大时,会严重影响sql执行的性能.一般单表到达几百万的时候,性能就会相对差一些了,这时就得分表了. 分表就是把一个表的数据放到多个表中,然后查询的时候

  • SpringBoot+MybatisPlus+Mysql+Sharding-JDBC分库分表

    目录 一.序言 1.组件及版本选择 2.预期目标 二.代码实现 (一)素材准备 1.实体类 2.Mapper类 3.全局配置文件 (二)增删查改 1.保存数据 2.查询列表数据 3.分页查询数据 4.查询详情 5.删除数据 6.修改数据 三.理论分析 1.选择分片列 2.扩容 一.序言 在实际业务中,单表数据增长较快,很容易达到数据瓶颈,比如单表百万级别数据量.当数据量继续增长时,数据的查询性能即使有索引的帮助下也不尽如意,这时可以引入数据分库分表技术. 本文将基于SpringBoot+Myba

  • Sharding-Proxy分库分表和数据加密使用场景分析

    目录 Sharding-Proxy分库分表和数据加密 使用场景 配置文件讲解 server.yaml config-sharding.yaml config-encrypt.yaml 其他 使用情况 总结 Sharding-Proxy分库分表和数据加密 主要将实际项目中使用shardingshpere-proxy的经历经验,总结分享一下. 使用场景 公司规划研发了两款针对政务新媒体和数字乡村的SaaS平台,作为新的利润增长点.考虑到以后的用户数量和数据数量,决定按照租户(签约客户)进行分库分表.

  • Springboot2.x+ShardingSphere实现分库分表的示例代码

    之前一篇文章中我们讲了基于Mysql8的读写分离(文末有链接),这次来说说分库分表的实现过程. 概念解析 垂直分片 按照业务拆分的方式称为垂直分片,又称为纵向拆分,它的核心理念是专库专用. 在拆分之前,一个数据库由多个数据表构成,每个表对应着不同的业务.而拆分之后,则是按照业务将表进行归类,分布到不同的数据库中,从而将压力分散至不同的数据库. 下图展示了根据业务需要,将用户表和订单表垂直分片到不同的数据库的方案. 垂直分片往往需要对架构和设计进行调整.通常来讲,是来不及应对互联网业务需求快速变化

随机推荐