一致性 Hash 算法 及Java TreeMap 实现

news2024/7/6 19:59:01

1、一致性 Hash 算法原理

一致性 Hash 算法通过构建环状的 Hash 空间替线性 Hash 空间的方法解决了这个问题,整个 Hash 空间被构建成一个首位相接的环。

其具体的构造过程为:

  1. 先构造一个长度为 2^32 的一致性 Hash 环
  2. 计算每个缓存服务器的 Hash 值,并记录,这就是它们在 Hash 环上的位置
  3. 对于每个图片,先根据 key 的 hashcode 得到它在 Hash 环上的位置,然后在 Hash 环上顺时针查找距离这个 Key 的 Hash 值最近的缓存服务器节点,这就是该图片所要存储的缓存服务器

当缓存服务器需要扩容的时候,只需要将新加入缓存服务器的 Hash 值放入一致性 Hash 环中,由于 Key 是顺时针查找距离其最近的节点,因此新加入的几点只影响整个环中的一小段。

加入新节点 NODE3 后,原来的 Key 大部分还能继续计算到原来的节点,只有 Key3、Key0 从原来的 NODE1 重新计算到 NODE3,这样就能保证大部分被缓存数据还可以命中。

当节点被删除时,其他节点在环上的映射不会发生改变,只是原来打在对应节点的 key 现在会转移到顺时针方向的下一个节点上。

2、解决Hash 环数据倾斜问题

新加入的节点 NODE3 只影响了原来的节点 NODE1,也就是说一部分原来需要访问 NODE1 的缓存数据现在需要访问 NODE3(概率上是 50%)但是原来的节点 NODE0 和 NODE2 不受影响,这就意味着 NODE0 和 NODE2 缓存数据量和负载压力是 NODE1 与 NODE3 的两倍。

(1)引入虚拟节点

为了解决这个问题,最好的办法就是扩展整个环上的节点数量,可以将每台物理缓存服务器虚拟为一组虚拟缓存服务器,使得 Hash 环在空间上的分割更加均匀。

这样只是将虚拟节点的 Hash 值放置在 Hash 环上,在查找时,首先根据 Key 值找到环上的虚拟节点,然后再根据虚拟节点找到真实的缓存服务器。

虚拟节点的数目足够多,就会使得节点在 Hash 环上的分布更加随机化,也就是增加或者删除一台缓存服务器时,都会较为均匀的影响原来集群中已经存在的缓存服务器。

3、一致性 Hash 算法的缺陷

虽然一致性 Hash 算法已经十分完善,但是还是有很多不足的地方

  1. Hash 环上的节点非常多或者更新频繁时,查询效率比较低下
  2. 整个 Hash 环需要一个服务路由来做负载均衡,存在单点问题

正因为一致性哈希分区的这些缺点,Redis 在实现自己的分布式集群方案时,采用了基于 P2P 结构的哈希槽算法。

其实本质上虚拟槽中的槽就是大量的虚拟节点的抽象化, 将原来的虚拟节点变成一个槽, redis内置是有16383个槽也就是有16383个虚拟节点。

(1)添加哈希槽解决更新效率

Redis Cluster 通过分片的方式将整个缓存划分为 16384 个槽,每个缓存节点就相当于 Hash 换上的一个节点,接入集群时,所有实例都将均匀占有这些哈希槽,当需要查询一个 Key 是,首先根据 Key 的 hashcode 对 16384 取余来得到 Key 属于哪个槽,并映射到缓存实例上。

(2)去中心化,解决单点负载

每个节点都保存有完整的哈希槽-节点的映射表,也就是说,每个节点都知道自己拥有哪些哈希槽,以及某个确定的哈希槽究竟对应着哪个节点。

无论向哪个节点发出寻找 Key 的请求,该节点都会通过求余计算该 Key 究竟存在于哪个哈希槽,并将请求转发至该哈希槽所在的节点。

4、使用ceilingEntry简易实现

这里使用TreeMap的ceilingEntry(K key) 方法,该方法用来返回与该键至少大于或等于给定键,如果不存在这样的键的键 - 值映射,则返回null相关联。

public class ConsistentHash {


    /**
     * 假设我们一共初始化有8个节点(可以是ip, 就理解为ip吧);
     * 把 1024个虚拟节点跟 8个资源节点相对应
     */
    public static Map<Integer, String> realNodeMap = new HashMap<>();
    public static int V_NODES = 1024; // 假设我们的环上有1024个虚拟节点
    static TreeMap<Integer, String> virtualNodeMap = new TreeMap<>();
    private static final Integer REAL_NODE_COUNT = 8;


    static {
        realNodeMap.put(0, "node_0");
        realNodeMap.put(1, "node_1");
        realNodeMap.put(2, "node_2");
        realNodeMap.put(3, "node_3");
        realNodeMap.put(4, "node_4");
        realNodeMap.put(5, "node_5");
        realNodeMap.put(6, "node_6");
        realNodeMap.put(7, "node_7");


        for (Integer i = 0; i < V_NODES; i++) {
            // 每个虚拟节点跟其取模的余数的 nodeMap 中的key相对应;
            // 下面删除虚拟节点的时候, 就可以根据取模规则来删除 TreeMap中的节点了;
            virtualNodeMap.put(i, realNodeMap.get(i % REAL_NODE_COUNT));
        }
    }




    /**
     * 输入一个id
     *
     * @param value
     * @return
     */
    public static String getRealServerNode(String value) {
        // 1. 传递来一个字符串, 得到它的hash值
        Integer vnode = value.hashCode() % 1024;
        // 2.找到对应节点最近的key的节点值
        String realNode = virtualNodeMap.ceilingEntry(vnode).getValue();


        return realNode;
    }


    /**
     * 模拟删掉一个物理可用资源节点, 其他资源可以返回其他节点
     *
     * @param nodeName
     */
    public static void dropBadNode(String nodeName) {
        int nodek = -1;
        // 1. 遍历 nodeMap 找到故障节点 nodeName对应的key;
        for (Map.Entry<Integer, String> entry : realNodeMap.entrySet()) {
            if (nodeName.equalsIgnoreCase(entry.getValue())) {
                nodek = entry.getKey();
                break;
            }
        }
        if (nodek == -1) {
            System.err.println(nodeName + "在真实资源节点中无法找到, 放弃删除虚拟节点!");
            return;
        }


        // 2. 根据故障节点的 key, 对应删除所有 chMap中的虚拟节点
        Iterator<Map.Entry<Integer, String>> iter = virtualNodeMap.entrySet().iterator();
        while (iter.hasNext()) {
            Map.Entry<Integer, String> entry = iter.next();
            int key = entry.getKey();
            String value = entry.getValue();
            if (key % REAL_NODE_COUNT == nodek) {
                System.out.println("删除节点虚拟节点: [" + key + " = " + value + "]");
                iter.remove();
            }
        }
    }


    public static void main(String[] args) {
        // 1. 一个字符串请求(比如请求字符串存储到8个节点中的某个实际节点);
        String requestValue = "hello";
        // 2. 打印虚拟节点和真实节点的对应关系;
        System.out.println(virtualNodeMap);
        // 3. 核心: 传入请求信息, 返回实际调用的节点信息
        System.out.println(getRealServerNode(requestValue));
        // 4. 删除某个虚拟节点后
        System.out.println("==========删除 node_2 之后: ================");
        dropBadNode("node_2");
        System.out.println("===============删除之后的虚拟节点map: ===========");
        System.out.println(virtualNodeMap);
        System.out.println("==============删除之后, 获取节点的真正node节点对应者: ");
        System.out.println(getRealServerNode(requestValue));


    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/449733.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

「C/C++」C++对已有的类进行扩充

博客主页&#xff1a;何曾参静谧的博客 文章专栏&#xff1a;「C/C」C/C学习 目录 相关术语一、 继承二、组合 相关术语 继承&#xff1a;继承父类后可以拥有父类对应的属性和方法。 组合&#xff1a;将类作为成员对象&#xff0c;基类可以直接调用派生类对应的属性和方法。 一…

MySQL_第08章_聚合函数

第08章_聚合函数 讲师&#xff1a;尚硅谷 - 宋红康&#xff08;江湖人称&#xff1a;康师傅&#xff09; 官网&#xff1a; http://www.atguigu.com 我们上一章讲到了 SQL 单行函数。实际上 SQL 函数还有一类&#xff0c;叫做聚合&#xff08;或聚集、分组&#xff09;函…

59 openEuler 22.03-LTS 搭建MySQL数据库服务器-软件介绍和配置环境

文章目录 59 openEuler 22.03-LTS 搭建MySQL数据库服务器-软件介绍和配置环境59.1 软件介绍59.2 配置环境59.2.1 关闭防火墙并取消开机自启动59.2.2 修改SELINUX为disabled59.2.3 创建组和用户59.2.4 创建数据盘59.2.4.1 方法一&#xff1a;在root权限下使用fdisk进行磁盘管理5…

JVM笔记 —— 垃圾回收(GC)详解

一、垃圾回收的分类 针对HotSpot JVM的实现&#xff0c;它里面的GC其实准确分类只有两大种: Partial GC&#xff1a;部分收集模式 Young GC&#xff1a;只收集年轻代的GCOld GC&#xff1a;只收集老年代的GC。只有CMS中有这个模式。Mixed GC&#xff1a;收集整个年轻代以及部分…

mybatis的基本使用和理解

mybatis的基本使用和理解 Lombok的使用(使用注解的方式将实体类中的get、set、构造函数代替&#xff09; Lombok是一个Java库&#xff0c;能自动插入编辑器并且构建工具&#xff0c;简化Java开发。通过添加注解的方式&#xff0c;不需要为类编写getter或equals方法&#xff0…

Vue3 + TS4.8踩坑之配置husky问题env: sh\r: No such file or directory

一、基本情况&#xff1a; 硬件环境&#xff1a;MacOS 10.14.6 背景&#xff1a;用vue3官方npm init vuelatest初始化创建的vue3 ts4.8项目。 二、问题和解决方案&#xff1a; 问题1&#xff1a;git commit的时候提示&#xff1a;env: sh\r: No such file or directory 原…

0121 进程管理

1.在Linux中&#xff0c;每个执行的程序都称为一个进程。每一个进程都分配一个ID号&#xff08;pid进程号&#xff09; 2.每个进程都可能以两种方式存在&#xff0c;前台和后台。前台进程即用户当前屏幕上可进行的操作&#xff0c;后台进程即实际操作&#xff0c;由于屏幕上无…

Java中源文件声明规则,以及java包,import关键字的使用

Java中源文件声明规则&#xff0c;以及java包&#xff0c;import关键字的使用。 1. 源文件声明规则 当在一个源文件中定义多个类&#xff0c;并且还有import语句和package语句时&#xff0c;要特别注意以下规则&#xff1a; a. 一个源文件中只能有一个 public 类。 b. 一个源…

LC电路是如何产生振荡的

电容和电感是两个储能元件&#xff0c;当电源给电容充电完成后&#xff0c; 将开关切到电感&#xff0c;电电感两端的电压是一个正弦波&#xff0c;正弦波频率是: 这时我们称电感和电容产生了振荡。 当然由于电感和电容都是有损耗的&#xff0c;所以这种振荡会慢慢的衰减&…

【文献查找使用】

目录 知识框架No.1 中文文献一、查找、二、下载、三、引用、页码四、什么是DOI呢&#xff1f;&#xff1f;&#xff1f; No.2 外文文献一、查找二、下载三、引用、页码 No.3 如何在不下载的情况下进行正确引用呢&#xff1f;&#xff1f;一、谷歌学术进行查询二、上sci-hub网站…

使用python的cartopy库读取shapefile文件 .shp文件是乱码

文章目录 问题解决方法 问题 使用python的cartopy库读取shapefile文件即.shp文件乱码 我在使用python的cartopy库读取shapefile文件时出现了乱码 record的.attributes的[‘name’]都是乱码 from cartopy.io import shapereader shp_pathr/home/mw/project/北京市1.shp#文件路…

【Linux 裸机篇(六)】I.MX6U 主频和时钟配置

目录 一、时钟系统详解1. 系统时钟来源2. 7路 PLL 时钟源2.1 ARM_PLL (PLL1)2.2 528_PLL (PLL2)2.3 USB1_PLL (PLL3)2.4 USB2_PLL (PLL7)2.5 ENET_PLL (PLL6)2.6 VIDEO_PLL (PLL5)2.7 AUDIO_PLL (PLL4) 3. 时钟树简介4. 内核时钟设置5. PFD 时钟设置6. AHB、 IPG 和 PERCLK 根时…

SQLite数据库简单小入门学习(一)

目录 一、认识数据库&#xff08;一&#xff09;数据库简介&#xff08;二&#xff09;数据库类型 二、SQLite数据库&#xff08;一&#xff09;SQLite简介&#xff08;二&#xff09;学习所需工具&#xff08;1&#xff09;scott.db&#xff08;2&#xff09;SQLiteSpy.exe &a…

【分布式理论】聊一下 ACID、BASE、CAP、FLP

分布式理论基础 今天我们来聊一下分布式相关基础理论基础&#xff0c;上一篇文章中&#xff0c;我描述了一下分布式系统的纲&#xff0c;但是想要入手学习分布式系统设计&#xff0c;其实需要先从基本理论开始。而知名的ACID、BASE、CAP、FLP都是相关的理论基础。 ACID ACID…

STM32平衡小车 TB6612电机驱动学习

TB6612FNG简介 单片机引脚的电流一般只有几十个毫安&#xff0c;无法驱动电机&#xff0c;因此一般是通过单片机控制电机驱动芯片进而控制电机。TB6612是比较常用的电机驱动芯片之一。 TB6612FNG可以同时控制两个电机&#xff0c;工作电流1.2A&#xff0c;最大电流3.2A。 VM电…

C++ [STL-简介]

本文已收录至《C语言和高级数据结构》专栏&#xff01; 作者&#xff1a;ARMCSKGT ​​​​​​​​ 文章目录 前言正文简介关于STL各种版本 STL组件容器迭代器配接器(适配器)算法仿函数空间配置器 STL的重要性学习STL的意义如何学习STL STL的缺陷 最后 前言 STL(standard tem…

Django+Vue的一个用户数据分析展示

文章目录 Git地址、项目所需文件总体效果展示一、项目环境所需二、Django代码解析2.1、执行文件2.2、注册app01 Git地址、项目所需文件 SQL数据文件和用户需求文件 提取码 1111 Git克隆地址 zip下载 其中第一个连接是数据文件&#xff0c;后两个连接选一个即可 总体效果展示 …

从idea向GitLab上传代码图文详解

这里写目录标题 一 新建一个idea工程二 点击左下角Version Control三 上传到GitLab四 给IDEA发链接五 回gitlab上校验六 去gitlab上把代码拖回来 在安装完gitlab插件&#xff0c;辛苦的配置完gitlab环境后&#xff0c;向gitlab中提交代码变成了首要任务 一 新建一个idea工程 二…

非常量引用只能绑定到左值

问题分析 代码情况&#xff1a; // ConsoleApplication1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 //#include <iostream> #include <vector> using namespace std; vector<int> fun1() {vector<int> ver1;for (int i…

HCIP之路---vlan实验

1、pc2/4/5/6同一网段&#xff0c;pc1/3与2/4/5/6不在同一网段 设置1/3 --- 192.168.1.0/24 2/4/5/6 --- 192.168.2.0/24 sw1上配置 [SW1-GigabitEthernet0/0/3]dis this # interface GigabitEthernet0/0/3 port link-type access port default vlan 2 # return [SW1-Giga…