欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 金融 > 一致性哈希

一致性哈希

2025/2/25 0:27:34 来源:https://blog.csdn.net/oopxiajun2011/article/details/143651685  浏览:    关键词:一致性哈希

问题呈现

如下图采用普通hash把数据存储到不同节点上。

问题1:当增加或删除一个节点时,需要重新分配大量的键,导致大量数据迁移和性能下降。

问题2:如果一个节点宕机,普通哈希并不会自动调整数据的分布,也不能平衡负载。

 

 如何改进呢?采用一致性hash可以解决这个问题。

当插入一个新的节点E,只有上屏100和101从节点A迁移到节点E。同理删除一个节点,也只有一个节点的数据会发生迁移。

 

一致性哈希(Consistent Hashing)是一种特殊的哈希算法,在分布式系统中具有广泛的应用。以下是对一致性哈希的理解及其解决的问题的详细阐述:

一、一致性哈希的定义

一致性哈希算法由麻省理工学院在1997年提出,目的是解决分布式缓存的问题。其核心思想是将整个哈希值空间组织成一个虚拟的环,通常使用MD5或SHA-1等哈希函数将数据项和服务器节点都映射到这个环上,并通过比较哈希值来确定数据应该存储在哪个节点上。这个哈希环的取值范围通常为0到2^32-1,形成一个闭环结构。

二、一致性哈希的工作原理

  1. 节点映射:使用哈希函数将每个服务器节点映射到哈希环上的某个点,这些点称为节点的哈希值或位置。
  2. 数据映射:使用相同的哈希函数将数据项映射到哈希环上的某个点。
  3. 数据分配:数据项在环上的位置确定后,从该位置开始顺时针查找,找到的第一个节点即为其存储节点。

三、一致性哈希解决的问题

  1. 动态伸缩性问题:在分布式系统中,节点的增加或减少是常见的操作。传统的哈希方法在面对这种动态变化时会导致大量的数据重新分配,因为哈希空间被完全重新映射。而一致性哈希通过在哈希环上顺时针查找来分配数据,当新节点加入或旧节点离开时,仅需重新分配少量数据,从而大大减少了数据迁移的范围和开销,保持了系统的高效性和稳定性。
  2. 负载均衡问题:一致性哈希通过将数据均匀分布在哈希环上,使得每个节点都能够承担相对均衡的负载。当节点数量增加时,新的节点会分担原有节点的负载,从而实现负载均衡。
  3. 容错性问题:在分布式系统中,节点故障是不可避免的。一致性哈希通过顺时针查找机制,使得当一个节点失效时,其负责的数据可以迅速重新分配到下一个节点上,从而保证了系统的容错性和可用性。

四、一致性哈希的改进与优化

  1. 引入虚拟节点:为了解决服务器节点分布不均匀的问题,一致性哈希引入了虚拟节点的概念。每个物理节点对应多个虚拟节点,数据映射到虚拟节点上,从而实现数据的均匀分布。当某个物理节点失效时,其对应的虚拟节点上的数据会均匀分散到其他节点的虚拟节点上,进一步提高了系统的容错性和负载均衡能力。
  2. 使用更高效的哈希函数:为了提高哈希计算的速度和准确性,一致性哈希可以使用更高效的哈希函数,如FNV-1a等。这些哈希函数具有较低的碰撞率和较高的散列均匀性,能够更好地满足分布式系统的需求。

 五、代码实现

实现一致性哈希(Consistent Hashing)在Java中需要一些基础的数据结构和算法。以下是一个简单的Java实现,它包括了基本的节点添加、删除和数据定位功能。请注意,这个实现是简化的,可能并不适合在生产环境中直接使用。 

首先,我们需要一个HashRing类来表示哈希环,以及一个HashNode类来表示环上的节点。

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;public class ConsistentHashing {// 虚拟节点的前缀,用于区分不同的物理节点private static final String VIRTUAL_NODE_PREFIX = "vnode_";// 虚拟节点的数量,可以根据需要调整private static final int VIRTUAL_NODE_COUNT = 100;// 存储节点和它们对应的哈希值private SortedMap<Integer, HashNode> circle = new TreeMap<>();// 存储物理节点和它们对应的虚拟节点集合private Map<String, List<HashNode>> realNodes = new ConcurrentHashMap<>();// 存储数据项和它们对应的节点private Map<String, String> dataMapping = new ConcurrentHashMap<>();// 哈希函数,用于计算哈希值private MessageDigest md;public ConsistentHashing() throws NoSuchAlgorithmException {md = MessageDigest.getInstance("MD5");}// 添加物理节点到哈希环public void addNode(String node) {List<HashNode> vnodeList = new ArrayList<>();for (int i = 0; i < VIRTUAL_NODE_COUNT; i++) {String virtualNodeName = VIRTUAL_NODE_PREFIX + node + "#" + i;int hash = hash(virtualNodeName);vnodeList.add(new HashNode(virtualNodeName, hash));circle.put(hash, new HashNode(virtualNodeName, hash));}realNodes.put(node, vnodeList);}// 从哈希环中移除物理节点public void removeNode(String node) {List<HashNode> vnodeList = realNodes.get(node);if (vnodeList != null) {for (HashNode vnode : vnodeList) {circle.remove(vnode.getHash());}realNodes.remove(node);}}// 获取数据项对应的节点public String getNode(String key) {if (circle.isEmpty()) {return null;}int hash = hash(key);SortedMap<Integer, HashNode> tailMap = circle.tailMap(hash);hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();return circle.get(hash).getVirtualNodeName().split("#")[0].replace(VIRTUAL_NODE_PREFIX, "");}// 添加数据项到哈希环public void put(String key, String value) {String node = getNode(key);dataMapping.put(key, node + ":" + value);}// 获取数据项的值public String get(String key) {return dataMapping.get(key);}// 计算哈希值private int hash(String key) {md.reset();md.update(key.getBytes(StandardCharsets.UTF_8));byte[] digest = md.digest();// 只取哈希值的前4个字节(32位)作为整数返回return ((digest[0] & 0xFF) << 24) |((digest[1] & 0xFF) << 16) |((digest[2] & 0xFF) << 8)  |(digest[3] & 0xFF);}// 辅助类:表示哈希环上的节点private static class HashNode {private final String virtualNodeName;private final int hash;public HashNode(String virtualNodeName, int hash) {this.virtualNodeName = virtualNodeName;this.hash = hash;}public String getVirtualNodeName() {return virtualNodeName;}public int getHash() {return hash;}}public static void main(String[] args) throws NoSuchAlgorithmException {ConsistentHashing ch = new ConsistentHashing();ch.addNode("NodeA");ch.addNode("NodeB");ch.addNode("NodeC");ch.put("key1", "value1");ch.put("key2", "value2");ch.put("key3", "value3");System.out.println("key1 is on node: " + ch.get("key1"));System.out.println("key2 is on node: " + ch.get("key2"));System.out.println("key3 is on node: " + ch.get("key3"));// 移除节点并查看数据重新分配情况ch.removeNode("NodeB");System.out.println("After removing NodeB:");System.out.println("key1 is now on node: " + ch.get("key1"));System.out.println("key2 is now on node: " + ch.get("key2"));System.out.println("key3 is now on node: " + ch.get("key3"));}
}

在这个实现中:

  • ConsistentHashing 类包含了哈希环、物理节点、数据项映射和哈希函数。
  • addNode 方法用于向哈希环中添加物理节点,并为每个物理节点创建多个虚拟节点。
  • removeNode 方法用于从哈希环中移除物理节点及其对应的虚拟节点。
  • getNode 方法用于根据数据项的哈希值找到其应该存储的物理节点。
  • put 和 get 方法用于在哈希环上存储和检索数据项。
  • hash 方法使用MD5哈希函数计算字符串的哈希值,并将其转换为整数。
  • HashNode 类是一个辅助类,用于表示哈希环上的节点。

请注意,这个实现中的哈希函数仅使用了MD5的前4个字节(32位)作为整数哈希值,这是为了简化示例。在生产环境中,您可能需要使用更完整的哈希值或更强大的哈希函数。此外,这个实现没有处理并发修改和数据一致性等高级问题。 

key1 is on node: NodeA:value1
key2 is on node: NodeA:value2
key3 is on node: NodeB:value3
After removing NodeB:
key1 is now on node: NodeA:value1
key2 is now on node: NodeA:value2
key3 is now on node: NodeB:value3

六、一致性哈希与普通哈希的区别

普通哈希(Hashing)与一致性哈希(Consistent Hashing)在数据分布、扩展性和容错性等方面存在显著区别。以下是对两者的详细比较:

6.1、核心思想与应用场景
  • 普通哈希

    • 核心思想:将输入(如键)通过哈希函数映射到固定范围内的一个值(如数组的索引)。
    • 应用场景:适用于静态系统或节点数量固定的情况,如哈希表或某些缓存场景。
  • 一致性哈希

    • 核心思想:在哈希空间中建立一个虚拟环,以减小节点的变动对整个系统的影响。
    • 应用场景:广泛用于分布式系统中,特别是节点数量可能动态变化的情况,如缓存系统(Redis、Memcached)、分布式存储系统、分布式数据库等。
6.2、数据分布与哈希空间
  • 普通哈希

    • 通常将输入映射到一个固定的哈希空间,如数组或固定大小的存储单元。
    • 设计目标是将输入均匀地分布在哈希空间中,以避免哈希冲突。
  • 一致性哈希

    • 将哈希值映射到一个环形结构上,节点和数据都在这个环上,数据映射到离它最近的节点上。
    • 使用了虚拟节点的概念,一个物理节点可以映射到多个哈希位置,有助于提高负载均衡性和避免数据倾斜问题。
6.3、扩展性与容错性
  • 普通哈希

    • 扩展性差:当增加或删除一个节点时,需要重新分配大量的键,导致大量数据迁移和性能下降。
    • 不具备容错性:如果一个节点宕机,普通哈希并不会自动调整数据的分布,也不能平衡负载。
  • 一致性哈希

    • 扩展性好:在增加或删除节点时,只需要重新分配少量的数据。新增一个节点后,只会将该节点附近的数据重新分配,而不会影响整个系统。
    • 容错性强:如果一个节点宕机,它的数据会自动映射到它周围的其他节点,减少数据丢失的风险。
6.4、具体实现与算法特性
  • 普通哈希

    • 实现相对简单,通常使用固定的哈希函数和哈希表。
    • 常见的哈希算法包括MD5、SHA-1等。
  • 一致性哈希

    • 实现相对复杂,需要设计一个环状结构,并将节点和数据映射到环上。
    • 使用了虚拟节点的概念来均衡负载。
    • 在增加或移除节点时,只有相关的少量节点参与到拓扑的维护中,具有较好的可扩展性和容错性。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词