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

引言

在分布式系统中,数据存储和访问的均匀性、高可用性及可扩展性至关重要。一致性哈希算法(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,一经查实,立即删除!

相关文章

快速了解AUTOSAR CP DEM模块作用与实现工作原理

在AUTOSAR&#xff08;Automotive Open System Architecture&#xff09;Classic Platform&#xff08;CP&#xff09;中&#xff0c;DEM&#xff08;Diagnostic Event Manager&#xff09;模块的主要作用是记录和管理ECU&#xff08;Electronic Control Unit&#xff09;内的诊…

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;会给依赖的配置文件写死…

2025届保研-优营率0%上岸C9

保研面试心得 以上是标题 说在前面 本人保研边缘人&#xff0c;基本不陶瓷老师&#xff08;因为没啥成果&#xff0c;但是没啥用&#xff0c;不如有offer再套&#xff0c;结果0offer&#xff09;&#xff0c;大三还在努力卷保研资格。绩点不高专业一般英语一般项目很水&#…

【C++刷题】力扣-#118-杨辉三角

题目描述 给定一个非负整数 numRows&#xff0c;生成杨辉三角的前 numRows 行。在杨辉三角中&#xff0c;每个数是它正上方两个数的和。 示例 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]示例 2: 输入: numRows 1 输出: [[1]]题解 这个问题…

Elasticsearch 入门

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

Leetcode 3321. Find X-Sum of All K-Long Subarrays II

Leetcode 3321. Find X-Sum of All K-Long Subarrays II 1. 解题思路2. 代码实现 题目链接&#xff1a;3321. Find X-Sum of All K-Long Subarrays II 1. 解题思路 这一题同样虽然是一道hard的题目&#xff0c;但也是比较常规的&#xff0c;就是通过一个滑动窗口不断地维护当…

【赵渝强老师】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;就大功告成了

MVC与MVVM

mvp mvvm区别 ‌MVP&#xff08;‌Model-View-Presenter&#xff09;和‌MVVM&#xff08;Model-View-ViewModel&#xff09;是两种常见的软件架构设计模式&#xff0c;它们在架构和通信方式上存在明显的区别。 ‌MVP模式‌&#xff1a; MVP是从MVC&#xff08;Model-View-Co…

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…

Spring 相关技术要点整理

以下是对 Bean 的作用域和生命周期的详细说明&#xff1a; 一、Bean 的作用域 singleton&#xff08;单例&#xff09;&#xff1a; 这是默认的作用域。在整个应用中&#xff0c;对于特定的 Bean 类型&#xff0c;只会创建一个实例。无论在应用的哪个地方获取该 Bean&#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…

C#中ref关键字和out关键字

值传递和引用传递 值传递和引用传递是编程中涉及数据传递的两种方式。它们的主要区别在于数据是如何在函数或方法之间传递的。 值传递 值传递意味着当你把一个变量传递给一个函数时&#xff0c;实际上传递的是这个变量的值的一个拷贝。也就是说&#xff0c;函数内部对这个参数…

深度学习之残差网络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