一致性 Hash 算法 及Java TreeMap 实现

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));


    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于Java+Spring+vue+element实现旅游信息管理平台系统

基于JavaSpringvueelement实现旅游信息管理平台系统 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式 文…

抢先看,甘特图工具DHTMLX gantt 灯箱编辑器通过套件 UI 小部件进行了扩展

DHTMLX Gantt是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表。可满足项目管理应用程序的大部分开发需求&#xff0c;具备完善的甘特图图表库&#xff0c;功能强大&#xff0c;价格便宜&#xff0c;提供丰富而灵活的JavaScript API接口&#xff0c;与各种服务器端技术&am…

为什么重写equals时必须重写hashCode()

不重写equals和不重写 hashCode()之前&#xff1a;equals()比较的是对象的内存地址&#xff0c;hashCode()比较的其实也是内存地址(内存地址输入到哈希函数中得到的整数) 重写了之后&#xff0c;equals()比较的是对象的内容值&#xff0c;如果hashCode()不重写&#xff0c;还是…

Android硬件通信之 WIFI通信

一&#xff0c;简介 1.1 随着网络的普及和通信技术的发展&#xff0c;网络的传输速度也越来越快&#xff0c;wifi技术也还成为手机设备最基本的配置。我们可以通过wifi实现手机与手机之前的信息传输&#xff0c;当然也可以与任意一台有wifi模块的其它设备传输。 1.2 wifi与蓝…

【数据库多表操作】sql语句基础及进阶

常用数据库&#xff1a; 数据库&#xff08;Database&#xff09;是按照数据结构来组织、存储和管理数据的仓库&#xff0c;它是长期存储在计算机内、有组织、有结构的数据集合。数据库是信息系统的核心部分&#xff0c;现代软件系统中大量采用了数据库管理系统&#xff08;DBM…

黑马在线教育数仓实战7

1. hive的相关的优化 1.1 hive的相关的函数(补充说明) if函数: 作用: 用于进行逻辑判断操作语法: if(条件, true返回信息,false返回信息) 注意: if函数支持嵌套使用 nvl函数: 作用: null值替换函数格式: nvl(T value, T default_value) COALESCE函数 作用: 非空查找函数:格式…

Node【Express框架【二】】

文章目录 &#x1f31f;前言&#x1f31f;中间件&#x1f31f;中间件函数&#x1f31f;什么是中间件函数&#x1f31f;中间件函数可以做什么 &#x1f31f;Express中间件的类型&#x1f31f;应用级中间件&#x1f31f;路由器级中间件&#x1f31f;错误处理中间件&#x1f31f;内…

华为OD机试真题(Java),计算最大乘积(100%通过+复盘思路)

一、题目描述 给定一个元素类型为小写字符串的数组&#xff0c;请计算两个没有相同字符的元素长度乘积的最大值&#xff0c; 如果没有符合条件的两个元素&#xff0c;返回0。 二、输入描述 输入为一个半角逗号分隔的小写字符串的数组&#xff0c;2 < 数组长度<100&am…

设计模式 --- 概述

一、设计模式概述 1.1、软件设计模式的产生背景 "设计模式"最初并不是出现在软件设计中&#xff0c;而是被用于建筑领域的设计中。 1977年美国著名建筑大师、加利福尼亚大学伯克利分校环境结构中心主任 克里斯托夫亚历山大 &#xff08;Christopher Alexander&…

机器学习算法 决策树

文章目录 一、决策树的原理二、决策树的构建2.1 ID3算法构建决策树2.2 C4.5 算法树的构建2.3 CART 树的创建 三、决策树的优缺点 一、决策树的原理 决策树&#xff08;Decision Tree&#xff09;是一种非参数的有监督学习方法&#xff0c;它能够从一系列有特征和标签的数据中总…

项目五:使用路由器构建园区网

使用路由器构建园区网 1、新建拓扑2、配置交换机与主机3、配置路由交换机并进行通信4、通信测试5、配置路由器并进行通信测试1、配置路由器R-12、配置路由器R-2、R-33、通信测试 1、新建拓扑 依次添加四台主机&#xff0c;两台交换机&#xff0c;型号为S3700。两台路由交换机&…

归排、计排深度理解

归并排序&#xff1a;是创建在归并操作上的一种有效的排序算法。算法是采用分治法&#xff08;Divide and Conquer&#xff09;的一个非常典型的应用&#xff0c;且各层分治递归可以同时进行。归并排序思路简单&#xff0c;速度仅次于快速排序&#xff0c;为稳定排序算法&#…

银行数字化转型导师坚鹏:数字化思维创新与金融业转型升级

数字化思维创新与金融业转型升级 课程背景&#xff1a; 很多金融机构存在以下问题&#xff1a; 金融机构的员工不知道需要具备什么样的数字化思维 不清楚数字化思维对金融机构转型升级的重要影响&#xff1f; 不清楚数字化背景下如何进行金融机构转型升级&#xff1f; …

flac格式如何转mp3,3招帮你搞定

flac格式如何转mp3&#xff0c;3招帮你搞定的方法来啦。当你的音频是flac格式是不是很头疼&#xff0c;又不知道怎么转mp3 。然后网上搜索出很多方法又不知道从哪个下手&#xff0c;是不是很疑惑&#xff1f;那今天就来看看小编推荐的方法吧&#xff0c;一定让你眼前一亮&#…

【机会约束、鲁棒优化】机会约束和鲁棒优化研究优化【ccDCOPF】研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

汽车制造数字化转型如何做?有哪些可行性案例?

引语&#xff1a;砥砺前行的先行者&#xff0c;为长期主义者带去曙光 国内制造企业亟需加速探索数字化转型之路。但是传统软件服务商提供的PLM、MES等系统已经无法满足企业个性化需求。通过传统软件服务商进行二次开发&#xff0c;成本高、周期长&#xff0c;难以适应迅速变化的…

威胁行为者针对云中的常见漏洞

Palo Alto Networks 已发布其第 42 单元云威胁报告的第 7 卷。该报告调查了 1300 多家组织。它分析了所有主要云服务提供商 (CSP) 的 210000 个云帐户、订阅和项目中的工作负载&#xff0c;为安全领导者和从业者提供了云安全的多方面视图。 云迁移的速度从 2021 年的 3700 亿…

图的存储及基本操作总结(邻接矩阵、邻接表)及C/C++代码实现

文章目录 前言一、邻接矩阵1.概念2.图像示例3. 代码实现注意邻接矩阵的特点 二、邻接表1.概念2.图像示例3.代码实现邻接表的特点 前言 图是一种比较复杂的数据结构&#xff0c;每个结点之间可以有多种关系。 所以&#xff0c;一个图可以呈现出千奇百怪的形式。 对于不同的形式…

java调用webservicer的方法

对于使用 Webservicer的方式&#xff0c;一般采用 Java API调用的方式。Webservicer是一个运行在浏览器中的客户端程序&#xff0c;它可以通过 Webservicer的接口来访问服务器上的服务。 使用 Java调用 Webservicer有两种方式&#xff1a; 下面是一个简单的例子&#xff1a; 2、…

【Vue】学习笔记-初始化脚手架

初始化脚手架 初始化脚手架说明具体步骤脚手架文件结构 初始化脚手架 说明 Vue脚手架是vue官方提供的标准化开发工具&#xff08;开发平台&#xff09;最新版本是4.x文档Vue CLI 具体步骤 如果下载缓慢请配置npm淘宝镜像 npm config set registry http://registry.npm.taoba…