从一致性哈希算法带来的分布式系统设计思考

引言

在分布式系统中,数据存储和访问的均匀性、高可用性及可扩展性至关重要。一致性哈希算法(Consistent Hashing)以其优秀的数据分布特性,广泛应用于缓存、负载均衡和数据库分片等领域,有效提升了系统的稳定性和灵活性。

一、一致性哈希是什么

一致性哈希(Consistent Hashing)是一种特殊的哈希算法,它主要用于解决分布式系统中数据的分布和负载均衡问题。一致性哈希的设计目标是在节点(如服务器或缓存设备)增加或移除时,能够最小化数据重新分布的次数,从而提高系统的稳定性和扩展性。

二、为什么提出一致性哈希

在讲解为什么提出一致性哈希算法之前,我们先来看看普通的哈希取模算法。以Redis分片集群为例,当用户需要查询某个数据时,首先会对查询的key进行哈希运算,然后通过对其总节点数量进行取模运算来确定访问哪一个Redis节点。例如:

image-20241017211906682

在这种情况下,可能会出现所有访问和存储操作恰好都映射到同一个Redis节点上的情况,从而对该节点造成巨大的压力。此外,不仅是数据存储分布不均的问题,如果需要新增一个Redis节点,现有的所有数据都需要重新进行哈希运算(rehash),以确定新的映射关系,这会导致扩展性非常差。

image-20241017212401798

因此,在面对上述问题的基础上,提出了一致性哈希算法来解决数据分布不均和扩展性差的问题

三、一致性哈希算法原理

一致性哈希算法的核心思想是将数据映射到一个固定范围的哈希环(环大小为2^32-1个节点)上,服务器节点也同样映射到这个哈希环上。数据根据其哈希值顺时针查找距离最近的服务器节点,从而完成数据的存储和访问。

还是拿上述分片集群为例:当采用一致性哈希后,Redis分片集群便成为了下面的样子:

image-20241017213211328

当需要在Redis节点2和Redis节点3之间添加一个新的Redis节点5时,只需要将原本属于Redis节点3的一部分数据迁移到新的Redis节点5上,仅影响到原Redis节点3的数据,并不需要额外影响其他节点。这样一来,实现了在扩容时的高扩展性。

image-20241017213427523

然而,一致性哈希环也会遇到一些问题,例如节点哈希分配不均匀,导致大量的请求集中在某些节点上(如Redis节点3和4),给这些节点带来很大的压力。

image-20241017213747405

为了解决这一问题,我们引入了虚拟节点的概念。通过在哈希环上为每个实际节点引入多个虚拟节点,可以使数据分布更加均匀。理论上来说,引入的虚拟节点越多,每个实际节点所受到的请求就越分散,从而减轻单个节点的压力。

image-20241017214100678

四、使用场景

一致性哈希算法的具体使用场景广泛存在于需要均匀接收请求并且能够实现高扩展性的分布式系统中。下面是一些典型的场景:

  1. 服务器的负载均衡

在Web服务器集群中,一致性哈希可以用来将请求均匀地分配到各个服务器上。当集群中的服务器数量发生变化时,只需重新分配受影响的数据,而不是重新哈希所有的请求。这减少了重新分配的开销,提高了系统的整体性能。

  1. Redis分片集群

在Redis集群中,一致性哈希算法用于将数据均匀地分布在多个Redis实例上。当集群需要扩展时(例如添加新的Redis节点),一致性哈希可以确保只有一小部分数据需要重新分布,从而降低了数据迁移的成本。

  1. 分布式文件系统

在分布式文件系统中,一致性哈希算法可以帮助将文件分布到多个存储节点上,确保数据的高可用性和负载均衡。当系统需要扩展或节点出现故障时,一致性哈希能够有效地重新分配数据,保证系统的稳定性。

  1. CDN(内容分发网络)

在内容分发网络中,一致性哈希可以用来将用户的请求路由到最近或最适合的服务节点上。这种方式不仅减少了网络延迟,还能提高系统的响应速度和服务质量。

实际上,一致性哈希算法还有许多其他应用场景,但由于篇幅限制,我们在这里不再一一展开详述。

五、Java实现

下面我们通过Java中的TreeMap类(红黑树)实现一个一致性哈希算法。

TreeMap是一种基于红黑树的数据结构,它可以对键进行排序,方便快速找到需要进行操作的节点。利用TreeMap的这一特性,我们可以高效地实现一致性哈希算法。

package com.example.provider.utils;import java.util.*;/*** 一致性哈希负载均衡器*/
public class ConsistentHashLoadBalancer {private final static SortedMap<Integer, String> hashRing = new TreeMap<>();private final int numberOfReplicas; // 每个物理节点的虚拟节点数/*** 构造函数** @param numberOfReplicas 每个物理节点的虚拟节点数* @param physicalNodes    物理节点列表*/public ConsistentHashLoadBalancer(int numberOfReplicas, Collection<String> physicalNodes) {this.numberOfReplicas = numberOfReplicas;for (String node : physicalNodes) {addNode(node);}}/*** 添加节点** @param node 要添加的节点*/public void addNode(String node) {for (int i = 0; i < numberOfReplicas; i++) {int hash = getHash(node + i);hashRing.put(hash, node);}}/*** 移除节点** @param node 要移除的节点*/public void removeNode(String node) {for (int i = 0; i < numberOfReplicas; i++) {int hash = getHash(node + i);hashRing.remove(hash);}}/*** 获取节点** @param key 数据的键* @return 负责该数据键的节点*/public String getNode(String key) {if (hashRing.isEmpty()) {return null;}int hash = getHash(key);if (!hashRing.containsKey(hash)) {SortedMap<Integer, String> tailMap = hashRing.tailMap(hash);hash = tailMap.isEmpty() ? hashRing.firstKey() : tailMap.firstKey();}return hashRing.get(hash);}/*** 获取哈希值** @param key 键* @return 哈希值*/static final int getHash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}public static void main(String[] args) {List<String> physicalNodes = Arrays.asList("Node1", "Node2", "Node3");ConsistentHashLoadBalancer loadBalancer = new ConsistentHashLoadBalancer(2, physicalNodes);hashRing.forEach((k, v) -> System.out.println(k + " -> " + v));// 测试数据定位for (int i = 0; i < 6; i++) {String node = loadBalancer.getNode("Key" + i);System.out.println("Key" + i + " is mapped to " + node);}// 移除一个节点loadBalancer.removeNode("Node2");System.out.println("\n移除一个节点  Node2:");for (int i = 0; i < 6; i++) {String node = loadBalancer.getNode("Key" + i);System.out.println("Key" + i + " is mapped to " + node);}hashRing.forEach((k, v) -> System.out.println(k + " -> " + v));// 增加一个节点loadBalancer.addNode("Node4");System.out.println("\n增加一个节点 Node4:");for (int i = 0; i < 6; i++) {String node = loadBalancer.getNode("Key" + i);System.out.println("Key" + i + " is mapped to " + node);}hashRing.forEach((k, v) -> System.out.println(k + " -> " + v));// 移除一个节点loadBalancer.removeNode("Node4");System.out.println("\n移除一个节点  Node4:");for (int i = 0; i < 6; i++) {String node = loadBalancer.getNode("Key" + i);System.out.println("Key" + i + " is mapped to " + node);}hashRing.forEach((k, v) -> System.out.println(k + " -> " + v));}
}

测试结果如下:

-1956273307 -> Node3
-1956270972 -> Node2
-1956270971 -> Node2
-1956270950 -> Node3
-1956270940 -> Node1
-1956270937 -> Node1
Key0 is mapped to Node3
Key1 is mapped to Node3
Key2 is mapped to Node3
Key3 is mapped to Node3
Key4 is mapped to Node3
Key5 is mapped to Node3移除一个节点  Node2:
Key0 is mapped to Node3
Key1 is mapped to Node3
Key2 is mapped to Node3
Key3 is mapped to Node3
Key4 is mapped to Node3
Key5 is mapped to Node3
-1956273307 -> Node3
-1956270950 -> Node3
-1956270940 -> Node1
-1956270937 -> Node1增加一个节点 Node4:
Key0 is mapped to Node3
Key1 is mapped to Node3
Key2 is mapped to Node3
Key3 is mapped to Node3
Key4 is mapped to Node3
Key5 is mapped to Node3
-1956273307 -> Node3
-1956273286 -> Node4
-1956273285 -> Node4
-1956270950 -> Node3
-1956270940 -> Node1
-1956270937 -> Node1移除一个节点  Node4:
Key0 is mapped to Node3
Key1 is mapped to Node3
Key2 is mapped to Node3
Key3 is mapped to Node3
Key4 is mapped to Node3
Key5 is mapped to Node3
-1956273307 -> Node3
-1956270950 -> Node3
-1956270940 -> Node1
-1956270937 -> Node1

五、总结

通过本文的学习,我们了解到一致性哈希算法为分布式系统带来了一系列重要的优势:

  1. 高扩展性:一致性哈希允许系统在增加或移除节点时,仅需重新分配受影响的数据,大大降低了数据迁移的成本。
  2. 均匀分布:通过将数据均匀分布到多个节点上,一致性哈希能够有效地避免单点过载,提高系统的整体性能。
  3. 高可用性:即使在部分节点失效的情况下,一致性哈希也能通过虚拟节点或其他节点来继续服务,保证系统的稳定性。
  4. 灵活的应用场景:一致性哈希不仅适用于缓存、负载均衡、数据库分片等场景,还在CDN、P2P网络、分布式文件系统等多种领域有着广泛的应用。

反思一致性哈希算法,我们可以从中获得以下几点启示:

  • 设计原则:在设计分布式系统时,考虑到系统的可扩展性和健壮性是非常重要的。一致性哈希算法给我们打了个样,展示了如何在不影响系统现有服务的前提下进行扩展。
  • 问题导向:任何技术都有其适用场景和局限性。一致性哈希虽然解决了许多问题,但也存在数据分布不均等挑战,比如说如何来设置一个合适的虚拟节点大小,太大则会增加负担,太小则会导致数据分配不均衡,实际还需要看具体的业务场景。

通过本文的学习,我们不仅掌握了一致性哈希算法的基本原理及其应用场景,更重要的是,它为我们提供了一个看待分布式系统设计的新视角,提醒着我们在未来的开发过程中更加注重系统的可扩展性和健壮性。

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

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

相关文章

uniapp onPageScroll

子组件有onPageScroll, 首页也要引入onPageScroll, eg: 主页面 sell/detail/index 《子组件》 <script setup> 引入onPageScroll </script> 组件&#xff1a; 引入onPageScroll 别人的比较

如果使用 Iptables 配置端口转发 ?

现实生活中&#xff0c;港口转发就像在一个大型公寓大楼里告诉送货司机该去哪里。通常情况下&#xff0c;该建筑群的正门是不对外开放的。但如果里面有人想要快递&#xff0c;他们可以告诉保安让司机进来&#xff0c;并指引他们到特定的公寓。 类似地&#xff0c;在计算机网络…

jeecg3版本的vue,离线启动

jeecg的vue2版本已经停止维护&#xff0c;所以只能用vue3的版本。3版本中使用的是pnpm&#xff08;npm的增强版本&#xff09;下载依赖。使用pnpm安装的node_modules&#xff0c;不能直接复制到离线主机中&#xff08;因为在 pnpm安装过程中&#xff0c;会给依赖的配置文件写死…

Elasticsearch 入门

ES 概述 ES 是一个开源的高扩展的分布式全文搜索引擎。 倒排索引 环境准备 Elasticsearch 官方地址&#xff1a;https://www.elastic.co/cn/ 下载地址&#xff1a; 注意&#xff1a;9300 端口为 Elasticsearch 集群间组件的通信端口&#xff0c;9200 端口为浏览器访问的 h…

【赵渝强老师】K8s中Deployment控制器与StatefulSet控制器的区别

一、K8s的Deployment与StatefulSets 在K8s中&#xff0c;Deployment将Pod部署成无状态的应用程序&#xff0c;它只关心Pod的数量、Pod更新方式、使用的镜像和资源限制等。由于是无状态的管理方式&#xff0c;因此Deployment中没有角色和顺序的概念&#xff0c;换句话说&#xf…

vue项目页面白边如何解决

这是出现白边的页面 原因是vue项目创建时在main.js下它引入了刚开始提供的main.css全局设置 直接把该设置注释掉即可&#xff0c; 然后在App.vue中添加如下style&#xff0c;就大功告成了

2025推荐选题|微信小程序实现经济新闻资讯

作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验&#xff0c;被多个学校常年聘为校外企业导师&#xff0c;指导学生毕业设计并参与学生毕业答辩指导&#xff0c;…

2.stm32 GPIO输出

GPIO简介 GPIO&#xff08;General Purpose Input Output&#xff09;通用输入输出口 可配置为8种输入输出模式 引脚电平&#xff1a;0V~3.3V&#xff0c;部分引脚可容忍5V 输出模式下可控制端口输出高低电平&#xff0c;用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等 …

tensorflow入门案例手写数字识别人工智能界的helloworld项目落地1

参考 https://tensorflow.google.cn/?hlzh-cn https://tensorflow.google.cn/tutorials/keras/classification?hlzh-cn 项目资源 https://download.csdn.net/download/AnalogElectronic/89872174 文章目录 一、案例学习1、导入测试和训练数据集&#xff0c;定义模型&#xff…

【R语言】随机森林+相关性热图组合图

数据概况文末有获取方式 随机森林部分 #调用R包 library(randomForest) library(rfPermute) library(ggplot2) library(psych) library(reshape2) library(patchwork) library(reshape2) library(RColorBrewer) ​ ​ #读取数据 df<-read.csv("F:\\EXCEL-元数据\\2020…

深度学习之残差网络ResNet

文章目录 1. 残差网络定义2. 数学基础函数类3. 残差块4.ResNet模型5.训练模型6.小结 1. 残差网络定义 随着我们设计的网络越来越深&#xff0c;深刻理解“新添加的层如何提升神经网络的性能”变得至关重要。更重要的是设计网络的能力。在这种网络中&#xff0c;添加层会使得网…

2010年国赛高教杯数学建模A题储油罐的变位识别与罐容表标定解题全过程文档及程序

2010年国赛高教杯数学建模 A题 储油罐的变位识别与罐容表标定 通常加油站都有若干个储存燃油的地下储油罐&#xff0c;并且一般都有与之配套的“油位计量管理系统”&#xff0c;采用流量计和油位计来测量进/出油量与罐内油位高度等数据&#xff0c;通过预先标定的罐容表&#…

JDBC的学习

一、JDBC DriverManager 二、JDBC connection 三、 JDBC Statement 1.DML 2.DDL 四、JDBC ResultSet 五、JDBC PreparedStatement

30.第二阶段x86游戏实战2-遍历周围-C++遍历二叉树(玩家角色基址)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要…

Prometheus运维监控平台之监控指标注册到consul脚本开发、自定义监控项采集配置调试(三)

系列文章目录 运维监控平台搭建 运维监控平台监控标签 golang_Consul代码实现Prometheus监控目标的注册以及动态发现与配置V1版本 文章目录 系列文章目录目的一、监控指标注册到consul的golang脚本开发1、修改settings.yaml文件2、修改config/ocnsul,go文件3、修改core/consul…

让你的MacOS剪切板变得更加强大,如何解决复制内容覆盖的问题

MacOS的日常使用过程中&#xff0c;肯定少不了复制粘贴&#xff0c;不论是文本内容还是文件&#xff0c;复制粘贴是避不开的操作&#xff0c;如果需要复制粘贴的内容不多&#xff0c;那么普通的复制粘贴就可以完成了&#xff0c;但是当有同样的内容需要输入不同的地方的时候&am…

C++的魔法世界:类和对象的终章

文章目录 一、再探构造函数二、类型转换2.1隐式类型转换2.2内置类型的类型转化2.3explicit关键字2.4多参数构造 三、static成员四、友元五、内部类内部类的特性 六、匿名对象 一、再探构造函数 类和对象(中)里介绍的构造函数&#xff0c;使用的是赋值实现成员变量的初始化。而…

出现接地故障电流现象,安科瑞ASJ剩余电流继电器可以避免吗?

什么是ASJ剩余电流继电器 剩余电流继电器是检测剩余电流&#xff0c;并将剩余电流值与基准值相比较的电器。当剩余电流值超过基准值时&#xff0c;它会发出一个机械开闭信号&#xff0c;使机械开关电器脱扣或声光报警装置发出报警。这种继电器通常基于漏电保护原理工作&#x…

【QAMISRA】解决导入commands.json时报错问题

【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 解决导入commands.json时报错“Could not obtain system-wide includes and defines”的问题。 2、 问题场景 客户导入commands.json时报错“Could not obtain system-wide includes and defines”。 3、软硬件环境…

【保姆级教程】DolphinScheduler本地部署与远程访问详细步骤解析

文章目录 前言1. 安装部署DolphinScheduler1.1 启动服务 2. 登录DolphinScheduler界面3. 安装内网穿透工具4. 配置Dolphin Scheduler公网地址5. 固定DolphinScheduler公网地址 前言 本篇教程和大家分享一下DolphinScheduler的安装部署及如何实现公网远程访问&#xff0c;结合内…