Sharding JDBC 分库分表(一致性Hash + 虚拟节点)

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

一、背景

传统的将数据集中存储至单一数据节点的解决方案,在性能、可用性和运维成本这三方面已经难于满足互联网的海量数据场景。

性能方面来说,由于关系型数据库大多采用B+树类型的索引,在数据量超过阈值的情况下,索引深度的增加也将使得磁盘访问的IO次数增加,进而导致查询性能的下降;同时,高并发访问请求也使得集中式数据库成为系统的最大瓶颈。

可用性的方面来讲,服务化的无状态型,能够达到较小成本的随意扩容,这必然导致系统的最终压力都落在数据库之上。而单一的数据节点,或者简单的主从架构,已经越来越难以承担。数据库的可用性,已成为整个系统的关键。

运维成本方面考虑,当一个数据库实例中的数据达到阈值以上,对于DBA的运维压力就会增大。数据备份和恢复的时间成本都将随着数据量的大小而愈发不可控。一般来讲,单一数据库实例的数据的阈值在1TB之内,是比较合理的范围。

在传统的关系型数据库无法满足互联网场景需要的情况下,将数据存储至原生支持分布式的NoSQL的尝试越来越多。 但NoSQL对SQL的不兼容性以及生态圈的不完善,使得它们在与关系型数据库的博弈中始终无法完成致命一击,而关系型数据库的地位却依然不可撼动。

数据分片指按照某个维度将存放在单一数据库中的数据分散地存放至多个数据库或表中以达到提升性能瓶颈以及可用性的效果。 数据分片的有效手段是对关系型数据库进行分库和分表。分库和分表均可以有效的避免由数据量超过可承受阈值而产生的查询瓶颈。 除此之外,分库还能够用于有效的分散对数据库单点的访问量;分表虽然无法缓解数据库压力,但却能够提供尽量将分布式事务转化为本地事务的可能,一旦涉及到跨库的更新操作,分布式事务往往会使问题变得复杂。 使用多主多从的分片方式,可以有效的避免数据单点,从而提升数据架构的可用性。

通过分库和分表进行数据的拆分来使得各个表的数据量保持在阈值以下,以及对流量进行疏导应对高访问量,是应对高并发和海量数据系统的有效手段。

二、挑战

虽然数据分片解决了性能、可用性以及单点备份恢复等问题,但分布式的架构在获得了收益的同时,也引入了新的问题。

面对如此散乱的分库分表之后的数据,应用开发工程师和数据库管理员对数据库的操作变得异常繁重就是其中的重要挑战之一。他们需要知道数据需要从哪个具体的数据库的分表中获取。另一个挑战则是,能够正确的运行在单节点数据库中的SQL,在分片之后的数据库中并不一定能够正确运行。例如,分表导致表名称的修改,或者分页、排序、聚合分组等操作的不正确处理。跨库事务也是分布式的数据库集群要面对的棘手事情。 合理采用分表,可以在降低单表数据量的情况下,尽量使用本地事务,善于使用同库不同表可有效避免分布式事务带来的麻烦。 在不能避免跨库事务的场景,有些业务仍然需要保持事务的一致性。 而基于XA的分布式事务由于在并发度高的场景中性能无法满足需要,并未被互联网巨头大规模使用,他们大多采用最终一致性的柔性事务代替强一致事务。

三、系统瓶颈

四、目标

五、分库分表策略

纵向切分

1、对于包含状态的数据分为冷热数据,对于还未完成的流程做为热数据实时更新信息,对于已完成的流程数据做为冷数据留档,用于信息追溯;

横向切分策略

1、 查询切分

记录id与db的关系,根据mapping关系分库

优点:Mapping 关系可以随时修改

缺点:增加了额外的单点

2、 范围切分

按照时间、ID或其它字段切分

优点:单表大小可控、天然支持水平扩展

缺点:无法解决集中写入瓶颈问题,如 按ID范围切分时,ID有序插入会优先进入其中一个DB,而其它DB此时未完全利用

3、 Hash切分(建议)

传统hash切分

通过取模算法 key.hashcode mod databases_num,将不同的数据分布在不同的数据库节点上。

优点:可以较为均匀的将数据切分

缺点:扩容时 需要重新计算切分后的值,涉及到大量的数据迁移

一致性Hash切分

Sharding JDBC 分库分表(一致性Hash + 虚拟节点)

建立一个2^32 hash节点环,计算database的hash值(常用:节点前缀 +(ip+端口).hahcode%2^32),并映射到hash环的具体位置,将待 切分 数据取值key.hashcode mod 2^32,映射到hash 环上,并按照规则从root节点开始顺时针查找,if node_1 < key_hash <= node_2 ;then data_key in node_2 如果循环一遍没有找到则data_key in node_root。

扩容时迁移数据时,如 新增节点在 node_1 ,node_2000 之间 node_1000,则只需要重新计算node_2000节点上的key_hash 重新分配即可(如果key_hash以持久化 则可以省略计算的步骤);

移除或节点异常下线时,如node_1,node_1000,node_2000 node_1000 下线,则node_1000的数据可以直接迁移到node_2000上

优点:扩容时只需要迁移小部分的数据

缺点:如果节点不够分散,会出现hash环的倾斜,导致节点数据不均匀

一致性Hash + 虚拟Hash节点

Sharding JDBC 分库分表(一致性Hash + 虚拟节点)

该思路基于一致性Hash 增加了虚拟节点,这些节点会映射到真实的物理节点,这样就可以调控节点数据分配

优点:减少了Hash环倾斜带来的影响

缺点:增加复杂度

实现方案

注:实现分库后,需要重新设计唯一ID

一致性Hash + 虚拟Hash节点

方案思路

基于sharding-jdbc 自定义分片算法

Sharding-JDBC 简介(详情参考官网:Sharding-JDBC :: ShardingSphere

Sharding-JDBC是ShardingSphere的第一个产品,也是ShardingSphere的前身。 它定位为轻量级Java框架,在Java的JDBC 层提供的额外服务。它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。

适用于任何基于JDBC的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。

支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。

支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer,PostgreSQL以及任何遵循SQL92标准的数据库。

实现步骤

1、集成sharding-jdbc

pom.xml

<!-- https://mvnrepository.com/artifact/org.apache.shardingsphere/sharding-jdbc-core -->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.1.1</version>
        </dependency>

新增sharding-jdbc 配置

spring:
  shardingsphere:
    #数据源配置,可配置多个data_source_name
    datasource:
      names: pdmp-0,pdmp-1
      pdmp-0:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://XXXXXX/pdmp-0?useUnicode=true&characterEncoding=UTF-8
        username: XXXXXX
        password: XXXXXX
      pdmp-1:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://XXXXXX/pdmp-1?useUnicode=true&characterEncoding=UTF-8
        username: XXXXXX
        password: XXXXXX
    sharding:
      #默认数据库分片策略
      default-database-strategy:
        inline:
          #分片算法行表达式,需符合groovy语法
          algorithm-expression: pdmp-$->{model_version_id % 2}
          sharding-column: model_version_id
      #数据表分片策略
      tables:
        feature_base:
          #由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式。缺省表示使用已知数据源与逻辑表名称生成数据节点,用于广播表(即每个库中都需要一个同样的表用于关联查询,多为字典表)或只分库不分表且所有库的表结构完全一致的情况
          actual-data-nodes: pdmp-$->{0..1}.feature_base$->{0..9}
          table-strategy:
            standard:
              #分片列名称
              sharding-column: image_uuid
              #精确分片算法类名称,用于=和IN。该类需实现PreciseShardingAlgorithm接口并提供无参数的构造器
              precise-algorithm-class-name: com.hanshow.afms.admin.sharding.ConsistentShardingAlgorithm
    props:
      sql:
        show: true

自定义分片算法

// 实现了两种算法 PreciseShardingAlgorithm(精准分片算法)、RangeShardingAlgorithm(范围分片算法)
public class ConsistentShardingAlgorithm
        implements PreciseShardingAlgorithm<Long>, RangeShardingAlgorithm<Long> {

    /**
     * 精确分片
     * 一致性hash算法
     */
    @Override
    public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> shardingValue) {

        //获取已经初始化的分表节点
        InitTableNodesToHashLoop initTableNodesToHashLoop =
                (InitTableNodesToHashLoop) SpringContextUtils.getBean(InitTableNodesToHashLoop.class);
        if (CollectionUtils.isEmpty(availableTargetNames)) {
            return shardingValue.getLogicTableName();
        }

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

        ConsistentHashAlgorithm consistentHashAlgorithm = new ConsistentHashAlgorithm(tableHashNode,
                availableTargetNames);

        return consistentHashAlgorithm.getTableNode(String.valueOf(shardingValue.getValue()));
    }

    /**
     * 范围查询规则
     * 可以根据实际场景进行修改
     * Sharding.
     *
     * @param availableTargetNames available data sources or tables's names
     * @param shardingValue        sharding value
     * @return sharding results for data sources or tables's names
     */
    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, RangeShardingValue<Long> shardingValue) {
        return availableTargetNames;
    }
}

hash环初始化

@Log4j2
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.getRuntimeContext().getRule();
            Collection<TableRule> tableRules = rule.getTableRules();
            ConsistentHashAlgorithm consistentHashAlgorithm = new ConsistentHashAlgorithm();
            for (TableRule tableRule : tableRules) {
                String logicTable = tableRule.getLogicTable();

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

一致散列算法

public class ConsistentHashAlgorithm {

    //虚拟节点,key表示虚拟节点的hash值,value表示虚拟节点的名称
    @Getter
    private SortedMap<Long, String> virtualNodes = new TreeMap<>();

    //当节点的数目很少时,容易造成数据的分布不均,所以增加虚拟节点来平均数据分布
    //虚拟节点的数目;虚拟节点的生成主要是用来让数据尽量均匀分布
    //虚拟节点是真实节点的不同映射而已
    //比如真实节点user1的hash值为100,那么我们增加3个虚拟节点user1-1、user1-2、user1-3,分别计算出来的hash值可能就为200,345,500;通过这种方式来将节点分布均匀
    private static final int VIRTUAL_NODES = 3;


    public ConsistentHashAlgorithm() {
    }


    public ConsistentHashAlgorithm(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 + "-VN" + 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.isNotBlank(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值
     *
     * @param key
     * @return
     */
    public long getHash(String 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;
    }

}

参考文章:https://blog.csdn.net/free_ant/article/details/111461606文章来源地址https://www.toymoban.com/news/detail-494824.html

到了这里,关于Sharding JDBC 分库分表(一致性Hash + 虚拟节点)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • springboot~对应sharding-jdbc实现分库分表

    当mysql数据库单表大于1千万以后,查询的性能就不能保证了,我们必须考虑分库,分表的方案了,还好,sharding-jdbc可以很优雅的与springboot对接,完成对mysql的分库和分表。 为了不影响其它小容量的表,所有添加了动态数据源,只对需要分库分表的进行配置即可 com.baomidou:dy

    2024年02月06日
    浏览(27)
  • springboot整合sharding-jdbc实现分库分表详解

    目录 一、为什么需要分库分表 1.1 分库分表的优势 二、分库分表基本概念 2.1 垂直分表

    2024年02月05日
    浏览(47)
  • Sharding-JDBC分库分表四种分片算法

    精确分片算法(PreciseShardingAlgorithm)精确分片算法(=与IN语句),用于处理使用单一键作为分片键的=与IN进行分片的场景。需要配合StandardShardingStrategy使用 范围分片算法(RangeShardingAlgorithm)用于处理使用单一键作为分片键的BETWEEN AND进行分片的场景。需要配合StandardShardingS

    2024年02月10日
    浏览(30)
  • Sharding-JDBC分库分表-自动配置与分片规则加载原理-3

    Sharding JDBC自动配置的原理 与所有starter一样,shardingsphere-jdbc-core-spring-boot-starter也是通过SPI自动配置的原理实现分库分表配置加载,spring.factories文件中的自动配置类shardingsphere-jdbc-core-spring-boot-starter功不可没,他主要是自动创建了模式bean、事务类型bean和数据源bean,配置加载

    2024年02月10日
    浏览(33)
  • 一、Sharding-JDBC系列01:整合SpringBoot实现分库分表,读写分离

    目录 一、概述 二、案例演示-水平分表 (1)、创建springboot工程 (2)、创建数据库和数据表 (3)、application.yaml配置分片规则   (4)、测试数据插入、查询操作 4.1、插入-控制台SQL日志  4.2、查询-控制台SQL日志  三、案例演示-水平分库 (1)、创建数据库和数据表  (2)、application.yaml配置

    2024年02月02日
    浏览(33)
  • 一致性hash负载均衡

    今天看下一致性hash,常见的负载均衡可能使用过hash,比如nginx中,如果使用session最简单就是通过hash,比如根据用户的请求ip进行hash,让不同用户的请求打到同一台服务器,这样状态处理起来最简单,对于session来说,如果现在重新上线了一台服务器,导致了所有请求hash之后

    2024年02月08日
    浏览(28)
  • Mybatis-Plus集成Sharding-JDBC与Flyway实现多租户分库分表

    公司产品部收到了一些重要客户的需求,他们希望能够依赖独立的数据库存储来支持他们的业务数据。与此同时,仍有许多中小客户,可以继续使用公共库以满足其需求。技术实现方面,此前持久层框架使用的Mybatis-plus,部分业务场景使用到了Sharding-JDBC用于分表,另外,我们

    2024年02月05日
    浏览(25)
  • Redis 原理缓存过期、一致性hash、雪崩、穿透、并发、布隆、缓存更新策略、缓存数据库一致性

    redis的过期策略可以通过配置文件进行配置 redis会把设置了过期时间的key放在单独的字典中,定时遍历来删除到期的key。 1).每100ms从过期字典中 随机挑选20个,把其中过期的key删除; 2).如果过期的key占比超过1/4,重复步骤1 为了保证不会循环过度,导致卡顿,扫描时间上限

    2024年02月08日
    浏览(43)
  • Redis扩容与一致性Hash算法解析

    作者:zhaokk 在分布式系统中,随着数据量的增加和负载的变化,对于存储系统的扩容变得尤为重要。Redis作为一种高性能的内存数据库,其在扩容方面采用了一致性Hash算法,以实现无缝的数据分布和负载均衡。本篇博客将详细探讨Redis的扩容机制,同时深入解析一致性Hash算法

    2024年02月12日
    浏览(39)
  • Ceph的crush算法与一致性hash对比介绍

    本文分享自天翼云开发者社区《Ceph的crush算法与一致性hash对比介绍》,作者:l****n 首先,我们先回顾下一致性hash以及其在经典存储系统中的应用。 一致性hash的基本原理 一致性hash的基本思想是,有一个hash函数,这个hash函数的值域形成了一个环(收尾相接:the largest hash value

    2024年04月23日
    浏览(21)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包