自学内容网 自学内容网

一致性hash和hash有什么区别?和代码实现

一致性哈希(Consistent Hashing)和普通哈希(Hashing)的区别主要体现在数据分布、扩展性和容错性上。它们的核心思想都是将输入(通常是键)映射到某个值(通常是一个哈希值),但应用场景和实现细节有所不同。以下是两者的主要区别:

1. 普通哈希(Hashing)

普通哈希是将输入(比如一个键)通过哈希函数映射到固定范围内的一个值(比如数组的索引)。常见的哈希算法包括 MD5、SHA-1 等。

特点:
  • 简单映射:普通哈希函数通常将输入映射到一个固定的哈希空间,比如数组或固定大小的存储单元。
  • 均匀性:哈希函数的设计目标是将输入均匀地分布在哈希空间中,以避免哈希冲突。
  • 固定结构:在分布式系统中,普通哈希通常将数据分配到有限数量的节点上(例如,固定数量的缓存或存储节点)。
缺点:
  • 扩展性差:当增加或删除一个节点时,普通哈希会重新分配大量的键。例如,如果你有 4 个存储节点并且需要添加一个新的节点,那么大部分数据可能需要重新分配,这会导致大量数据迁移,性能下降。
  • 不具备容错性:在分布式系统中,如果一个节点宕机,普通哈希并不会自动调整数据的分布,也不能平衡负载。

2. 一致性哈希(Consistent Hashing)

一致性哈希是一种改进的哈希算法,主要用于分布式系统,尤其是当节点数量可能动态变化时(如增加或删除节点)。它通过在哈希空间中建立一个虚拟环,来减小节点的变动对整个系统的影响。

特点:
  • 哈希环:一致性哈希将哈希值映射到一个环形结构上。节点和数据都在这个环上,数据映射到离它最近的节点上。
  • 扩展性好:在一致性哈希中,增加或删除节点时,只需要重新分配少量的数据。例如,新增一个节点后,只会将该节点附近的数据重新分配,而不会影响整个系统。
  • 容错性强:一致性哈希支持容错。如果一个节点宕机,它的数据会自动映射到它周围的其他节点,减少数据丢失的风险。
  • 虚拟节点:为了提高负载均衡性,一致性哈希通常使用虚拟节点,将一个物理节点映射到多个哈希位置。这有助于避免数据倾斜问题,即某些节点负载过高。
优点:
  • 负载平衡:由于数据按需重新分配,扩展或缩减节点时,整个系统负载更平衡。
  • 高容错:在节点故障时,数据可以被其他节点接管,减少宕机带来的影响。
  • 可扩展性强:新增或删除节点时只会影响较少的键。

区别总结:

特性普通哈希(Hashing)一致性哈希(Consistent Hashing)
数据分布固定的哈希空间,数据均匀分布哈希环上的分布,数据靠近其节点
扩展性扩展或减少节点需要重新分配大量数据扩展或减少节点只影响部分数据
容错性宕机时没有自动恢复机制宕机时数据可由其他节点接管
适用场景固定数量的节点或存储单元动态扩展的分布式系统,负载均衡

使用场景:

  • 普通哈希:适用于静态系统,节点数量固定的情况下使用,如哈希表或某些缓存场景。
  • 一致性哈希:广泛用于分布式系统中,特别是在缓存系统(如 Redis、Memcached)、分布式存储系统、分布式数据库等场景中,节点数量经常变动的情况下。

例子:

  • 普通哈希:在缓存系统中,数据通过 hash(key) % n 的方式映射到 n 个缓存节点之一。添加或删除一个节点需要重新计算所有数据的映射。

  • 一致性哈希:在分布式缓存系统中,假设你有 4 个缓存节点,当添加第 5 个节点时,只有一部分数据会被重新映射到新节点上,而不是重新映射所有的数据。

一致性哈希在动态扩展和容错性方面有明显优势,是分布式系统中常用的一种哈希方式。

普通hash算法

package com.aixiao.business.controller;
import java.util.HashMap;
import java.util.Map;
public class HashExample {
    public static void main(String[] args) {
        // 定义三个服务器节点
        String[] servers = {"Server1", "Server2", "Server3"};

        // 模拟请求键值
        String[] keys = {"Key1", "Key2", "Key3", "Key4", "Key5"};

        // 哈希键并将其分配到服务器上
        for (String key : keys) {
            int serverIndex = key.hashCode() % servers.length;
            // 防止负数的索引值
            serverIndex = (serverIndex < 0) ? -serverIndex : serverIndex;
            System.out.println("Key: " + key + " is assigned to " + servers[serverIndex]);
        }
    }
}

一致性hash

 一致性哈希的实现相对复杂一些,关键是要设计一个环状结构,并将节点和数据映射到环上。我们将物理节点映射到多个虚拟节点,以均衡负载,并确保在增加或移除节点时,只有一部分数据需要重新分配。

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.SortedMap;
import java.util.TreeMap;
public class ConsistentHashing {
    // 哈希环
    private final SortedMap<Integer, String> hashRing = new TreeMap<>();
    // 虚拟节点数量
    private static final int VIRTUAL_NODES = 3;

    // 添加服务器节点到哈希环
    public void addServer(String server) {
        for (int i = 0; i < VIRTUAL_NODES; i++) {
            int hash = hash(server + i);
            hashRing.put(hash, server);
            System.out.println("Added server: " + server + " with virtual node hash: " + hash);
        }
    }

    // 根据键查找所属的服务器节点
    public String getServer(String key) {
        int hash = hash(key);
        SortedMap<Integer, String> tailMap = hashRing.tailMap(hash);

        // 如果没有比当前键哈希更大的节点,则取环的第一个节点
        Integer nodeHash = tailMap.isEmpty() ? hashRing.firstKey() : tailMap.firstKey();
        return hashRing.get(nodeHash);
    }

    // 使用MD5哈希函数将键值转换为哈希值
    private int hash(String key) {
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            byte[] digest = md5.digest(key.getBytes(StandardCharsets.UTF_8));
            return ((digest[3] & 0xFF) << 24) | ((digest[2] & 0xFF) << 16) | ((digest[1] & 0xFF) << 8) | (digest[0] & 0xFF);
        } catch (Exception e) {
            throw new RuntimeException("MD5 Hash error", e);
        }
    }

    public static void main(String[] args) {
        ConsistentHashing consistentHashing = new ConsistentHashing();

        // 添加服务器节点
        consistentHashing.addServer("Server1");
        consistentHashing.addServer("Server2");
        consistentHashing.addServer("Server3");

        // 模拟请求的键
        String[] keys = {"Key1", "Key2", "Key3", "Key4", "Key5"};

        // 查找键对应的服务器
        for (String key : keys) {
            System.out.println("Key: " + key + " is mapped to " + consistentHashing.getServer(key));
        }
    }
}

代码解释:

  1. 虚拟节点:每个服务器(Server1, Server2, Server3)在哈希环上映射了多个虚拟节点。这样做是为了避免数据倾斜问题,提高负载均衡。
  2. 哈希环:我们使用 TreeMap 来存储哈希值,TreeMap 自动排序,方便我们找到合适的节点。
  3. 查找服务器节点:当查找一个键对应的服务器时,我们先计算它的哈希值,然后在哈希环上找到大于等于该哈希值的第一个节点。如果没有,则循环回到哈希环的第一个节点。

一致性哈希的优点:

  • 节点扩展或减少时只影响少量数据:当新增或删除服务器节点时,只有少量数据需要重新分配,而不是全部数据。
  • 负载均衡:虚拟节点帮助均衡每个物理节点的负载。

总结:

  • 普通哈希:适用于节点固定且不变动的简单场景,扩展性差,所有数据都要重新分配。
  • 一致性哈希:适合分布式系统中动态节点变动的场景,扩展性好,容错性强,适合如分布式缓存、存储等系统。

原文地址:https://blog.csdn.net/ykp92/article/details/142367565

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!