Sharding-JDBC 自定义一致性哈希算法 + 虚拟节点 实现数据库分片策略

2023-11-04

1. Sharding-JDBC 分片策略

分片操作是分片键 + 分片算法,也就是分片策略。目前Sharding-JDBC 支持多种分片策略:

  • 标准分片策略
    对应StandardShardingStrategy。提供对SQL语句中的=, IN和BETWEEN AND的分片操作支持。
  • 复合分片策略
    对应ComplexShardingStrategy。复合分片策略。提供对SQL语句中的=, IN和BETWEEN AND的分片操作支持。
  • 行表达式分片策略
    对应InlineShardingStrategy。使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。
  • Hint分片策略
    对应HintShardingStrategy。通过Hint而非SQL解析的方式分片的策略。

具体的请参考官方文档:sharding分片策略

2. Sharding-JDBC 分片算法

分片算法需要应用方开发者自行实现,可实现的灵活度非常高。

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

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

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

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

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

3. 自定义一致性哈希算法实践

什么是一致性哈希算法 请看这篇博客:

  1. 首先实现ShardingJDBC提供的接口:
    • PreciseShardingAlgorithm:精确分片算法,如果使用 == 来判定分片的情况,需要实现这个接口。
    • RangeShardingAlgorithm:范围分片,如果有使用范围查找的话,需要使用这个进行分片策略。

/**
 * 自定义哈希算法 + 虚拟节点实现数据分片
 * 使用FNV1_32_HASH算法计算key的Hash值
 * 也可以使用 MurmurHash3 或者别的加密方式
 * @author manji
 * @Date 2023/5/4
 */
public class ConsistenceHashAlgorithm implements RangeShardingAlgorithm<Long>, PreciseShardingAlgorithm<Long> {
    /**
     * 范围查找时需要用到改分片算法,这里暂不完善了
     * @param collection
     * @param rangeShardingValue
     * @return
     */
    @Override
    public Collection<String> doSharding(Collection collection, RangeShardingValue rangeShardingValue) {
        System.out.println(collection);
        System.out.println(rangeShardingValue);
        return collection;
    }

    /**
     * @param collection collection 配置文件中解析到的所有分片节点
     * @param preciseShardingValue 解析到的sql值
     * @return 
     */
    @Override
    public String doSharding(Collection collection, PreciseShardingValue preciseShardingValue) {
        System.out.println(collection);
        InitTableNodesToHashLoop initTableNodesToHashLoop = SpringUtils.getBean(InitTableNodesToHashLoop.class);
        if (CollectionUtils.isEmpty(collection)) {
            return preciseShardingValue.getLogicTableName();
        }

        //这里主要为了兼容当联表查询时,如果两个表非关联表则
        //当对副表分表时shardingValue这里传递进来的依然是主表的名称,
        //但availableTargetNames中确是副表名称,所有这里要从availableTargetNames中匹配真实表
        ArrayList<String> availableTargetNameList = new ArrayList<>(collection);
        String logicTableName = availableTargetNameList.get(0).replaceAll("[^(a-zA-Z_)]", "");
        SortedMap<Long, String> tableHashNode =
                initTableNodesToHashLoop .getTableVirtualNodes().get(logicTableName);

        ConsistenceHashUtil consistentHashAlgorithm = new ConsistenceHashUtil(tableHashNode,
                collection);

        return consistentHashAlgorithm.getTableNode(String.valueOf(preciseShardingValue.getValue()));
    }
}
  1. 初始化hash环
    注意@Lazy 不添加的话会报错ShardingDataSource 找不到,因为在加载该类时,ShardingDataSource 还未放入容器中,所以获取不到,所以使用@Lazy 注解延后该类的加载。
/**
 * 初始化hash环
 * @author manji
 * @Date 2023/5/4
 */
@Slf4j
@Component
@Lazy
public class InitTableNodesToHashLoop {

    @Resource
    private ShardingDataSource shardingDataSource;

    @Getter
    private HashMap<String, SortedMap<Long, String>> tableVirtualNodes = new HashMap<>();

    @PostConstruct
    public void init() {
        try {
            ShardingRule rule = shardingDataSource.getShardingContext().getShardingRule();
            Collection<TableRule> tableRules = rule.getTableRules();
            ConsistenceHashUtil consistenceHashUtil = new ConsistenceHashUtil();
            for (TableRule tableRule : tableRules) {
                String logicTable = tableRule.getLogicTable();

                tableVirtualNodes.put(logicTable,
                        consistenceHashUtil.initNodesToHashLoop(
                                tableRule.getActualDataNodes()
                                        .stream()
                                        .map(DataNode::getTableName)
                                        .collect(Collectors.toList()))
                );
            }
        } catch (Exception e) {
            log.error("分表节点初始化失败 {}", e);
        }
    }
}

  1. 一致性hash算法的核心代码

/**
 * 一致性哈希算法工具类
 * @author manji
 * @Date 2023/5/4
 */
public class ConsistenceHashUtil {

    //存储所有节点,按照hash值排序的
    @Getter
    private SortedMap<Long, String> virtualNodes = new TreeMap<>();

    // 设置虚拟节点的个数
    private static final int VIRTUAL_NODES = 3;


    public ConsistenceHashUtil() {
    }


    public ConsistenceHashUtil(SortedMap<Long, String> virtualTableNodes, Collection<String> tableNodes) {
        if (Objects.isNull(virtualTableNodes)) {
            virtualTableNodes = initNodesToHashLoop(tableNodes);
        }

        this.virtualNodes = virtualTableNodes;
    }

    public SortedMap<Long, String> initNodesToHashLoop(Collection<String> tableNodes) {
        SortedMap<Long, String> virtualTableNodes = new TreeMap<>();
        for (String node : tableNodes) {
            for (int i = 0; i < VIRTUAL_NODES; i++) {
                String s = String.valueOf(i);
                String virtualNodeName = node + "-manji" + s;
                long hash = getHash(virtualNodeName);

                virtualTableNodes.put(hash, virtualNodeName);
            }
        }

        return virtualTableNodes;
    }

    /**
     * 通过计算key的hash
     * 计算映射的表节点
     *
     * @param key
     * @return
     */
    public String getTableNode(String key) {
        String virtualNode = getVirtualTableNode(key);
        //虚拟节点名称截取后获取真实节点
        if (!StringUtils.isEmpty(virtualNode)) {
            return virtualNode.substring(0, virtualNode.indexOf("-"));
        }
        return null;
    }

    /**
     * 获取虚拟节点
     * @param key
     * @return
     */
    public String getVirtualTableNode(String key) {
        long hash = getHash(key);
        // 得到大于该Hash值的所有Map
        SortedMap<Long, String> subMap = virtualNodes.tailMap(hash);
        String virtualNode;
        if (subMap.isEmpty()) {
            //如果没有比该key的hash值大的,则从第一个node开始
            Long i = virtualNodes.firstKey();
            //返回对应的服务器
            virtualNode = virtualNodes.get(i);
        } else {
            //第一个Key就是顺时针过去离node最近的那个结点
            Long i = subMap.firstKey();
            //返回对应的服务器
            virtualNode = subMap.get(i);
        }

        return virtualNode;
    }

    /**
     * 使用FNV1_32_HASH算法计算key的Hash值
     * 也可以使用 MurmurHash3 或者别的加密方式
     * @param key
     * @return
     */
    public long getHash(String key) {
//        return MurmurHash3.murmurhash3_x86_32(key);
        final int p = 16777619;
        int hash = (int) 2166136261L;
        for (int i = 0; i < key.length(); i++)
            hash = (hash ^ key.charAt(i)) * p;
        hash += hash << 13;
        hash ^= hash >> 7;
        hash += hash << 3;
        hash ^= hash >> 17;
        hash += hash << 5;

        // 如果算出来的值为负数则取其绝对值
        if (hash < 0)
            hash = Math.abs(hash);
        return hash;
    }

}

  1. 配置Sharding-JDBC的分片策略

spring.main.allow-bean-definition-overriding = true

mybatis.configuration.map-underscore-to-camel-case = true

#数据源
spring.shardingsphere.datasource.names = m1

#数据源1
spring.shardingsphere.datasource.m1.type = com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name = com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.m1.url = jdbc:mysql://localhost:3306/shardingdemo?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
spring.shardingsphere.datasource.m1.username = root
spring.shardingsphere.datasource.m1.password = 12345678


spring.shardingsphere.sharding.default-data-source-name=m1

# 指定user表的数据分布情况
spring.shardingsphere.sharding.tables.user.actual-data-nodes = m1.user_$->{0..2}
# 指定user表的分片策略,分片策略包括分片键和分片算法
spring.shardingsphere.sharding.tables.user.table-strategy.standard.sharding-column=id
spring.shardingsphere.sharding.tables.user.table-strategy.standard.precise-algorithm-class-name=com.manji.shardingdemo.consistencehasg.ConsistenceHashAlgorithm


# 打开sql输出日志
spring.shardingsphere.props.sql.show = true

代码demo地址sharding-一致性哈希算法

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Sharding-JDBC 自定义一致性哈希算法 + 虚拟节点 实现数据库分片策略 的相关文章

随机推荐

  • 【Mysql数据库全套教程笔记-SELECT使用篇】

    Mysql基础篇 SELECT使用篇 SQL之SELECT使用篇 第03章 基本的SELECT语句 第04章 运算符 第05章 排序与分页 第06章 多表查询 第07章 单行函数 第08章 聚合函数 第09章 子查询 我做的笔记 希望能帮助
  • java2.0——数据类型

    目录 一 字面常量 二 数据类型 1 引用数据类型 2 基本数据类型 注意 三 变量 1 语法格式 2 各变量中的注意事项 3 类型转换 1 自动类型转换 隐式 2 强制类型转换 显式 3 类型提升 四 字符串类型 一 字面常量 常量即程序
  • C# params 用法简介

    params 是C 的关键字 可变长参数 是在声明方法时参数类型或者个数不确定时使用 关于params 参数数组 需掌握以下几点 一 参数数组必须是一维数组 二 不允许将params修饰符与ref和out修饰符组合起来使用 三 与参数数组对
  • 两个大一网页设计期末课设-HTML+CSS+JS前端课设(附下载链接)

    大一网页设计期末课设 HTML CSS JS前端课设 1 公司网站模板 2 游戏网站 1 公司网站模板 点我该项目下载资源 https download csdn net download weixin 43474701 87941280
  • Silicon Labs CP210x USB to UART Bridge,COM口无法识别

    一直用一个电脑端 识别USB转串口的函数接口 但在Silicon Labs CP210x USB to UART Bridge上 却无法成功识别 确认过Silicon Labs CP210x USB to UART Bridge和Proli
  • 设计原则:开闭原则

    开闭原则 OCP 开闭原则的英文全称是 Open Closed Principle 简写为 OCP 它的英文描述是 softwareentities modules classes functions etc should be open
  • Selenium 屏蔽 webdriver被识别出来 的一些解决办法

    问题描述 使用 selenium 模拟浏览器进行数据抓取是目前最通用的爬虫方案 所见即所得 通吃各种数据加载方式 能绕过JS加密 爬虫检测 签名机制 但是Selenium依然能被检测到 它在运行时会暴露出一些预定义的JavaScript变量
  • led灯条串联图_灯带/LED灯条的安装,安装接线示意图

    灯带由于发光颜色多变 可调光 可控制颜色变化 经常会被用于家装中的装饰作用 而且有些小地方 1 灯带 LED灯条的安装方法 每款灯带的背后都贴有自粘性3M双面胶 安装时可以直接撕去3M双面胶表面的贴纸 然后把灯带固定在安装位置 用手把它贴紧
  • luffy配置相关

    目录 后台配置之封装logger 在项目中集成日志 封装全局异常 二次封装response 数据库配置 在项目中配置使用mysql User模块User表配置 开放media访问 路飞前台项目创建和配置 elementui vue cook
  • (附源码)计算机毕业设计ssm车位管理系统

    项目运行 环境配置 Jdk1 8 Tomcat7 0 Mysql HBuilderX Webstorm也行 Eclispe IntelliJ IDEA Eclispe MyEclispe Sts都支持 项目技术 SSM mybatis Ma
  • 深入浅出SSD

    深入浅出SSD https download csdn net download vb748 19408058
  • Elasticsearch multi-index join实践

    Elasticsearch 多索引 join 实践 注 本文采用的实现语言是python 用到了python中的第三方库 作者 aideny 前言 博主近期在开发一个解析引擎 把我们定义的json格式查询语言解析成多种数据源的查询语言 然后
  • uni-app开发微信小程序,动态修改底部tabbar文字及顶部导航栏文字方法

    修改顶部导航文字 uni setNavigationBarTitle title res data data name 修改底部tabbar文字 uni setTabBarItem index 2 title res data data n
  • 恢复目录数据库发生 ORACLE 错误: ORA-00955: 名称已由现有对象使用

    author skate time 2009 02 19 在创建catalog的时候 如果报如下的错误 可以采用 drop catalog 然后再创建的方法解决 或直接运行spdrop sql 原因可能是以前创建过catalog RMAN
  • c语言程序 输入一个四位数,用c语言编程:输入一个四位数,求出它的个位、十位、百位、千位...

    满意答案 QQ89748770 推荐于 2018 02 26 采纳率 45 等级 13 已帮助 7318人 C代码 int a scanf d a printf 个位 d 十位 d 百位 d 千位 d a 10 a 100 10 a 100
  • python 使用exchange发送邮件

    安装库exchangelib pip install exchangelib 脚本内容 coding utf 8 Created on 2018 2 from exchangelib import DELEGATE Account Cred
  • 2.1矩阵的概念

    矩阵有什么用 矩阵可以用于表示复杂的信息 关系 下图左边是航班图 用 1 表示有航班 0 为无航班信息 这样就可以把左图转化到一张表 这里我联想到了自然语言处理中的词文档共现矩阵 图像处理中每一副数字图像也是一个矩阵 矩阵的定义 直观印象
  • MC9S12XEP100的ATD模块(ADC12B16CV1)

    网上的各种示例基本都是用同步 轮询的方式来使用ATD模块的 自己封装ATD模块时想利用中断改成异步的方式 结果出现了莫名其妙的问题 我明明没有开启比较中断的 结果还是跳到了比较中断里头去了 一气之下 把整个文档翻译了一遍 顺带给大家分享了
  • 什么是mAP(mean average Precision)

    Mean Average Precision 即 平均AP值 AP Average precision 单类标签平均 各个召回率中最大精确率的平均数 的精确率 AP PR Precision Recall 曲线下面积 mAP Mean Av
  • Sharding-JDBC 自定义一致性哈希算法 + 虚拟节点 实现数据库分片策略

    1 Sharding JDBC 分片策略 分片操作是分片键 分片算法 也就是分片策略 目前Sharding JDBC 支持多种分片策略 标准分片策略 对应StandardShardingStrategy 提供对SQL语句中的 IN和BETW