真的是最全的一致性hash环讲解了

一致性 hash 环

最近做项目 做了一个分发器 ,需要 根据请求携带的参数 把请求分发到 不同的服务器上面,最终我选择使用 一致性hash 环 来实现 ,本篇 就主要讲解一下 一致性hash环 它的基本原理

概述

一致性hash算法 由于 均衡性 持久性的映射特点 被广泛应用于负载均衡领域,比如 nginx 、dubbo 、等等 内部都有一致性hash 的实现 ,比如 dubbo ,当你调用rpc 接口的时候,如果有2个提供者,那么你可以通过配置 让其调用通过 一致性hash 进行计算 然后分发到具体的 某个实例接口上 。

1.hash算法 在负载均衡中的问题

先来看看普通的hash算法的特点,普通的hash算法就是把一系列输入 打散成随机的数据,负载均衡就是利用这一点特性,对于大量请求调用,通过一定的 hash将它们均匀地散列,从而实现压力平均化

如果上面图中 key作为缓存的key ,node中寸入该key对应的 value,就是一个 简单的分布式缓存系统了。

问题:可以看出 当 N 节点数发生变化的时候 之前所有的 hash映射几乎全部失效,如果集群是无状态的服务 倒是没什么事情,但是如果是 分布式缓存这种功能,比如 映射的key1 原本是去 node1上查询 缓存的value1, 但是当N节点变化后 hash后的 key1 可能去了 node2 这样 就产生了致命的问题。。

2.一致性 hash 算法

一致性hash算法就是来解决 上面的问题

2.1 特点 (重要)

下面说明 一致性hash算法的 2个 重要的特点

  • 平衡性
  • 平衡性是指哈希的结果能够尽可能分布到所有的缓冲空间中去,这样可以使得所有的缓冲空间都得到利用。很多哈希算法都能够满足这一条件。
  • 单调性
  • 单调性是指如果已经有一些内容通过哈希分派到了相应的缓冲中,又有新的缓冲区加入到系统中,那么哈希的结果应能够保证原有已分配的内容可以被映射到新的缓冲区中去,而不会被映射到旧的缓冲集合中的其他缓冲区。 简单的哈希算法往往不能满足单调性的要求

2.2 原理

一致性哈希将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-2^32-1(即哈希值是一个32位无符号整形),整个哈希空间环如下:

就是所有的输入值都被映射到 0-2^32-1 之间,组成一个圆环

下一步将各个服务器使用Hash进行一个哈希, 具体可以选择服务器的ip或主机名或者其他业务属性作为关键字 进行哈希,这样每台机器就能确定其在哈希环上的位置,这里假设将上文中四台服务器使用ip地址哈希后在环空间的位置如下:

如果服务器数量不多且个数相对稳定,我们也可以手动设置这些服务器的位置,如设A在2

30-1位置,B在2

31-1位置,C在3*2

30-1位置,D在2

32-1位置,则hash值为02^30-1的数据存储在A中,hash值为2^30-12^31-1的数据存储在B中,以此类推。

**定位数据存储的方法: **将数据key使用相同的函数Hash计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器。可以为这些服务器维护一条二分查找树,定位服务器的过程就是在二分查找树中找到好比其大的节点。

例如我们有Object A、Object B、Object C、Object D四个数据对象,经过哈希计算后,在环空间上的位置如下:

2.3 一致性hash 优点

如上图,假如当节点A宕机了,那么只会影响 objectA 它会被重新映射到 NodeB节点,其他的ObjectB ObjectC ObjectD 都不会受到影响,大大提高了容错性和稳定性

2.4 一致性hash存在的问题

2.3.1 数据分布不均匀

当节点Node很少的时候 比如2台机器,那么 必然造成大量数据集中在NodeA,少量在NodeB

2.3.2 雪崩

当某个节点宕机后,原本属于它的请求 都会被重新hash 映射到 下游节点,会突然造成下游节点压力过大 有可能也会造成 下游节点宕机,从而形成雪崩 这是致命的

为此 引入了 虚拟节点来解决上面两个问题

3.带虚拟节点的一致性hash

就是会在圆环上 根据Node 节点 生成很多的虚拟节点 分布在圆环上,这样当 某个 节点挂了后 原本属于它的请求,会被均衡的分布到 其他节点上 降低了产生雪崩的情况,也解决了 节点数少导致 请求分布不均的请求

即对每一个服务节点计算多个哈希(可以用原节点key+"##xxxk"作为每个虚拟节点的key,然后求hashcode),每个计算结果位置都放置一个此服务节点,称为虚拟节点。 具体做法可以在服务器ip或主机名的后面增加编号来实现。

看上图,此时如果 group3节点挂了,那么请求会被均分到 group2 和 group1上面 ,到此 一致性hash的正确生产的使用方式讲解完了 下面来看看一个案例代码。

4. 代码测试

可供选择的有很多,memcached官方使用了基于md5的KETAMA算法,但这里处于计算效率的考虑,使用了FNV1_32_HASH算法,如下:

public class HashUtil {
    /**
     * 计算Hash值, 使用FNV1_32_HASH算法
     * @param str
     * @return
     */
    public static int getHash(String str) {
        final int p = 16777619;
        int hash = (int)2166136261L;
        for (int i = 0; i < str.length(); i++) {
            hash =( hash ^ str.charAt(i) ) * p;
        }
        hash += hash << 13;
        hash ^= hash >> 7;
        hash += hash << 3;
        hash ^= hash >> 17;
        hash += hash << 5;

        if (hash < 0) {
            hash = Math.abs(hash);
        }
        return hash;
    }
}
package com.weareint.dispatchservice.hashloop;

import org.springframework.stereotype.Component;

import java.util.*;

/**
 *
 *
 * <pre>
 *  一致性 hash 虚拟 环
 * </pre>
 *
 * @author johnny
 * @date 2021-08-26 9:22 上午
 */
@Component
public class HashVirtualNodeCircle {

    /** 真实集群列表 */
    private static List<String> instanceNodes;

    /** 虚拟节点映射关系 */
    private static SortedMap<Integer, String> virtualNodes = new TreeMap<>();

    /** 虚拟节点数 */
    private static final int VIRTUAL_NODE_NUM = 1000;

    /** 刷新 服务实例 */
    public void refreshVirtualHashCircle(HashCircleInstanceNodeBuild build) {
        // 当集群变动时,刷新hash环,其余的集群在hash环上的位置不会发生变动
        virtualNodes.clear();
        // 获取 最新节点
        instanceNodes = build.instanceNodes();
        // 将虚拟节点映射到Hash环上
        for (String realInstance : instanceNodes) {
            for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
                String virtualNodeName = getVirtualNodeName(realInstance, i);
                int hash = HashUtils.getHash(virtualNodeName);
                System.out.println("[" + virtualNodeName + "] launched @ " + hash);
                virtualNodes.put(hash, virtualNodeName);
            }
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值