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

这篇具有很好参考价值的文章主要介绍了Sharding-JDBC 自定义一致性哈希算法 + 虚拟节点 实现数据库分片策略。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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-一致性哈希算法文章来源地址https://www.toymoban.com/news/detail-433934.html

到了这里,关于Sharding-JDBC 自定义一致性哈希算法 + 虚拟节点 实现数据库分片策略的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 区块链:哈希算法与一致性哈希算法

    本篇主要介绍区块链中常用到的哈希算法。 1 哈希算法 1.1 定义及特性   哈希算法是指通过哈希函数(Hash Function)对任意长度的输入数据(比如文件、消息、数字等)进行转换,生成一个固定长度的哈希值(Hash Value)的过程。   在区块链中,哈希算法常用于区块验证及安全性保

    2024年02月17日
    浏览(50)
  • 【分布式】一致性哈希和哈希槽

    当我们拥有了多台存储服务器之后,现在有多个key,希望可以将这些个key均匀的缓存到这些服务器上,可以使用哪些方案呢? 1.1 直接哈希取模 这是一种最容易想到的方法,使用取模算法hash(key)% N,对key进行hash运算后取模,N是机器的数量。key进行hash后的结果对3取模,得

    2024年02月03日
    浏览(29)
  • 【负载均衡——一致性哈希算法】

    一致性哈希算法就很好地解决了分布式系统在扩容或者缩容时,发生过多的数据迁移的问题。 一致哈希算法也用了取模运算,但与哈希算法不同的是,哈希算法是对节点的数量进行取模运算,而一致 哈希算法 是对 2^32 进行取模运算,是一个固定的值。 一致性哈希要进行两步

    2024年04月10日
    浏览(41)
  • 07. 算法之一致性哈希算法介绍

    哈希算法在程序开发中的很多地方都能看到他的身影,但是哈希有他的局限性,比如如果两个key哈希到同一个位置的时候,此时就不好处理。本节我们介绍一下常规处理方式。 哈希算法将任意长度的二进制值映射为较短的固定长度的二进制值,这个小的二进制值称为哈希值。

    2024年02月06日
    浏览(38)
  • Python小知识 - 一致性哈希算法

    一致性哈希算法 一致性哈希算法(Consistent Hashing Algorithm)是用于解决分布式系统中节点增减比较频繁的问题。它的思想是,将数据映射到0~2^64-1的哈希空间中,并通过哈希函数对数据进行映射,计算出数据所在的节点。当节点增加或减少时,只需要重新计算数据所在的节点即

    2024年02月09日
    浏览(39)
  • 一致性哈希算法优势在哪?如何实现?

    1.1 简介Hash 哈希算法即散列算法,是一种从任意文件中创造小的数字「指纹」的方法。与指纹一样,散列算法就是一种以较短的信息来保证文件唯一性的标志,这种标志与文件的每一个字节都相关,而且难以找到逆向规律。因此,当原有文件发生改变时,其标志值也会发生改

    2024年02月03日
    浏览(41)
  • Dubbo负载均衡策略之 一致性哈希

    本文主要讲解了一致性哈希算法的原理以及其存在的数据倾斜的问题,然后引出解决数据倾斜问题的方法,最后分析一致性哈希算法在Dubbo中的使用。通过这篇文章,可以了解到一致性哈希算法的原理以及这种算法存在的问题和解决方案。 在这里引用dubbo官网的一段话——

    2024年02月08日
    浏览(47)
  • Redis扩容机制与一致性哈希算法解析

    在分布式系统设计中,Redis是一个备受欢迎的内存数据库,而一致性哈希算法则是分布式系统中常用的数据分片和负载均衡技术。本文将深入探讨Redis的扩容机制以及一致性哈希算法的原理,同时提供示例代码以帮助读者更好地理解这两个重要概念。 Redis是一种高性能的内存数

    2024年02月11日
    浏览(35)
  • 得物面试:Redis用哈希槽,而不是一致性哈希,为什么?

    在40岁老架构师 尼恩的 读者交流群 (50+)中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题: Redis为何用哈希槽而不用一致性哈希? 最近有小伙伴在面试网易,又遇到了相关的面试题。

    2024年02月21日
    浏览(37)
  • Dubbo负载均衡策略之 一致性哈希 | 京东云技术团队

    本文主要讲解了一致性哈希算法的原理以及其存在的数据倾斜的问题,然后引出解决数据倾斜问题的方法,最后分析一致性哈希算法在Dubbo中的使用。通过这篇文章,可以了解到一致性哈希算法的原理以及这种算法存在的问题和解决方案。 在这里引用dubbo官网的一段话——

    2024年02月08日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包