0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Dubbo负载均衡策略之一致性哈希

OSC开源社区 来源:京东技术 2023-06-16 15:30 次阅读

导读

本文主要讲解了一致性哈希算法的原理以及其存在的数据倾斜的问题,然后引出解决数据倾斜问题的方法,最后分析一致性哈希算法在Dubbo中的使用。通过这篇文章,可以了解到一致性哈希算法的原理以及这种算法存在的问题和解决方案。

01负载均衡

在这里引用dubbo官网的一段话——

LoadBalance 中文意思为负载均衡,它的职责是将网络请求,或者其他形式的负载“均摊”到不同的机器上。避免集群中部分服务器压力过大,而另一些服务器比较空闲的情况。通过负载均衡,可以让每台服务器获取到适合自己处理能力的负载。在为高负载服务器分流的同时,还可以避免资源浪费,一举两得。负载均衡可分为软件负载均衡和硬件负载均衡。在我们日常开发中,一般很难接触到硬件负载均衡。但软件负载均衡还是可以接触到的,比如 Nginx。在 Dubbo 中,也有负载均衡的概念和相应的实现。Dubbo 需要对服务消费者的调用请求进行分配,避免少数服务提供者负载过大。服务提供者负载过大,会导致部分请求超时。因此将负载均衡到每个服务提供者上,是非常必要的。Dubbo 提供了4种负载均衡实现,分别是基于权重随机算法的 RandomLoadBalance、基于最少活跃调用数算法的 LeastActiveLoadBalance、基于hash 一致性的 ConsistentHashLoadBalance,以及基于加权轮询算法的 RoundRobinLoadBalance。这几个负载均衡算法代码不是很长,但是想看懂也不是很容易,需要对这几个算法的原理有一定了解才行。

02 哈希算法

7d315652-0c16-11ee-962d-dac502259ad0.png

图1无哈希算法请求

如上所示,假设0,1,2号服务器都存储的有用户信息,那么当我们需要获取某用户信息时,因为我们不知道该用户信息存放在哪一台服务器中,所以需要分别查询0,1,2号服务器。这样获取数据的效率是极低的。

对于这样的场景,我们可以引入哈希算法。

7d532782-0c16-11ee-962d-dac502259ad0.png

图2引入哈希算法后的请求

还是上面的场景,但前提是每一台服务器存放用户信息时是根据某一种哈希算法存放的。所以取用户信息的时候,也按照同样的哈希算法取即可。

假设我们要查询用户号为100的用户信息,经过某个哈希算法,比如这里的userId mod n,即100 mod 3结果为1。所以用户号100的这个请求最终会被1号服务器接收并处理。

这样就解决了无效查询的问题。

但是这样的方案会带来什么问题呢?

扩容或者缩容时,会导致大量的数据迁移。最少也会影响50%的数据。

7d6e16a0-0c16-11ee-962d-dac502259ad0.png

图3增加一台服务器

为了说明问题,加入一台服务器3。服务器的数量n就从3变成了4。还是查询用户号为100的用户信息时,100 mod 4结果为0。这时,请求就被0号服务器接收了。

当服务器数量为3时,用户号为100的请求会被1号服务器处理。

当服务器数量为4时,用户号为100的请求会被0号服务器处理。

所以,当服务器数量增加或者减少时,一定会涉及到大量数据迁移的问题。

对于上述哈希算法其优点是简单易用,大多数分库分表规则就采取的这种方式。一般是提前根据数据量,预先估算好分区数。

其缺点是由于扩容或收缩节点导致节点数量变化时,节点的映射关系需要重新计算,会导致数据进行迁移。所以扩容时通常采用翻倍扩容,避免数据映射全部被打乱,导致全量迁移的情况,这样只会发生50%的数据迁移。

03一致性哈希算法

一致性 hash 算法由麻省理工学院的 Karger 及其合作者于1997年提出的,算法提出之初是用于大规模缓存系统的负载均衡。它的工作过程是这样的,首先根据 ip 或者其他的信息为缓存节点生成一个 hash,并将这个 hash 投射到 [0, 232 - 1] 的圆环上。当有查询或写入请求时,则为缓存项的 key 生成一个 hash 值。然后查找第一个大于或等于该 hash 值的缓存节点,并到这个节点中查询或写入缓存项。如果当前节点挂了,则在下一次查询或写入缓存时,为缓存项查找另一个大于其 hash 值的缓存节点即可。大致效果如下图所示,每个缓存节点在圆环上占据一个位置。如果缓存项的 key 的 hash 值小于缓存节点 hash 值,则到该缓存节点中存储或读取缓存项。比如下面绿色点对应的缓存项将会被存储到 cache-2 节点中。由于 cache-3 挂了,原本应该存到该节点中的缓存项最终会存储到cache-4节点中。

7daecbaa-0c16-11ee-962d-dac502259ad0.png

图4一致性哈希算法

在一致性哈希算法中,不管是增加节点,还是宕机节点,受影响的区间仅仅是增加或者宕机服务器在哈希环空间中,逆时针方向遇到的第一台服务器之间的区间,其它区间不会受到影响。

但是一致性哈希也是存在问题的:

7dda212e-0c16-11ee-962d-dac502259ad0.png

图5数据倾斜

当节点很少的时候可能会出现这样的分布情况,A服务会承担大部分请求。这种情况就叫做数据倾斜。

那如何解决数据倾斜的问题呢?

加入虚拟节点。

首先一个服务器根据需要可以有多个虚拟节点。假设一台服务器有n个虚拟节点。那么哈希计算时,可以使用IP+端口+编号的形式进行哈希值计算。其中的编号就是0到n的数字。由于IP+端口是一样的,所以这n个节点都是指向的同一台机器。

7e009e30-0c16-11ee-962d-dac502259ad0.png

图6引入虚拟节点

在没有加入虚拟节点之前,A服务器承担了绝大多数的请求。但是假设每个服务器有一个虚拟节点(A-1,B-1,C-1),经过哈希计算后落在了如上图所示的位置。那么A服务器的承担的请求就在一定程度上(图中标注了五角星的部分)分摊给了B-1、C-1虚拟节点,实际上就是分摊给了B、C服务器。

一致性哈希算法中,加入虚拟节点,可以解决数据倾斜问题。

04一致性哈希在DUBBO中的应用

7e38a140-0c16-11ee-962d-dac502259ad0.png

图7Dubbo中一致性哈希环

这里相同颜色的节点均属于同一个服务提供者,比如 Invoker1-1,Invoker1-2,……, Invoker1-160。这样做的目的是通过引入虚拟节点,让 Invoker 在圆环上分散开来,避免数据倾斜问题。所谓数据倾斜是指,由于节点不够分散,导致大量请求落到了同一个节点上,而其他节点只会接收到了少量请求的情况。比如:

7e6dc262-0c16-11ee-962d-dac502259ad0.png

图8数据倾斜问题

如上,由于 Invoker-1 和 Invoker-2 在圆环上分布不均,导致系统中75%的请求都会落到 Invoker-1 上,只有 25% 的请求会落到 Invoker-2 上。解决这个问题办法是引入虚拟节点,通过虚拟节点均衡各个节点的请求量。

到这里背景知识就普及完了,接下来开始分析源码。我们先从 ConsistentHashLoadBalance 的 doSelect 方法开始看起,如下:

public class ConsistentHashLoadBalance extends AbstractLoadBalance {


    private final ConcurrentMap> selectors = 
        new ConcurrentHashMap>();


    @Override
    protected  Invoker doSelect(List> invokers, URL url, Invocation invocation) {
        String methodName = RpcUtils.getMethodName(invocation);
        String key = invokers.get(0).getUrl().getServiceKey() + "." + methodName;


        // 获取 invokers 原始的 hashcode
        int identityHashCode = System.identityHashCode(invokers);
        ConsistentHashSelector selector = (ConsistentHashSelector) selectors.get(key);
        // 如果 invokers 是一个新的 List 对象,意味着服务提供者数量发生了变化,可能新增也可能减少了。
        // 此时 selector.identityHashCode != identityHashCode 条件成立
        if (selector == null || selector.identityHashCode != identityHashCode) {
            // 创建新的 ConsistentHashSelector
            selectors.put(key, new ConsistentHashSelector(invokers, methodName, identityHashCode));
            selector = (ConsistentHashSelector) selectors.get(key);
        }


        // 调用 ConsistentHashSelector 的 select 方法选择 Invoker
        return selector.select(invocation);
    }
    
    private static final class ConsistentHashSelector {...}
}

如上,doSelect 方法主要做了一些前置工作,比如检测 invokers 列表是不是变动过,以及创建 ConsistentHashSelector。这些工作做完后,接下来开始调用 ConsistentHashSelector 的 select 方法执行负载均衡逻辑。在分析 select 方法之前,我们先来看一下一致性 hash 选择器 ConsistentHashSelector 的初始化过程,如下:

private static final class ConsistentHashSelector {


    // 使用 TreeMap 存储 Invoker 虚拟节点
    private final TreeMap> virtualInvokers;


    private final int replicaNumber;


    private final int identityHashCode;


    private final int[] argumentIndex;


    ConsistentHashSelector(List> invokers, String methodName, int identityHashCode) {
        this.virtualInvokers = new TreeMap>();
        this.identityHashCode = identityHashCode;
        URL url = invokers.get(0).getUrl();
        // 获取虚拟节点数,默认为160
        this.replicaNumber = url.getMethodParameter(methodName, "hash.nodes", 160);
        // 获取参与 hash 计算的参数下标值,默认对第一个参数进行 hash 运算
        String[] index = Constants.COMMA_SPLIT_PATTERN.split(url.getMethodParameter(methodName, "hash.arguments", "0"));
        argumentIndex = new int[index.length];
        for (int i = 0; i < index.length; i++) {
            argumentIndex[i] = Integer.parseInt(index[i]);
        }
        for (Invoker invoker : invokers) {
            String address = invoker.getUrl().getAddress();
            for (int i = 0; i < replicaNumber / 4; i++) {
                // 对 address + i 进行 md5 运算,得到一个长度为16的字节数组
                byte[] digest = md5(address + i);
                // 对 digest 部分字节进行4次 hash 运算,得到四个不同的 long 型正整数
                for (int h = 0; h < 4; h++) {
                    // h = 0 时,取 digest 中下标为 0 ~ 3 的4个字节进行位运算
                    // h = 1 时,取 digest 中下标为 4 ~ 7 的4个字节进行位运算
                    // h = 2, h = 3 时过程同上
                    long m = hash(digest, h);
                    // 将 hash 到 invoker 的映射关系存储到 virtualInvokers 中,
                    // virtualInvokers 需要提供高效的查询操作,因此选用 TreeMap 作为存储结构
                    virtualInvokers.put(m, invoker);
                }
            }
        }
    }
}

ConsistentHashSelector 的构造方法执行了一系列的初始化逻辑,比如从配置中获取虚拟节点数以及参与 hash 计算的参数下标,默认情况下只使用第一个参数进行 hash。需要特别说明的是,ConsistentHashLoadBalance 的负载均衡逻辑只受参数值影响,具有相同参数值的请求将会被分配给同一个服务提供者。ConsistentHashLoadBalance 不 关系权重,因此使用时需要注意一下。

在获取虚拟节点数和参数下标配置后,接下来要做的事情是计算虚拟节点 hash 值,并将虚拟节点存储到 TreeMap 中。到此,ConsistentHashSelector 初始化工作就完成了。接下来,我们来看看 select 方法的逻辑。

public Invoker select(Invocation invocation) {
    // 将参数转为 key
    String key = toKey(invocation.getArguments());
    // 对参数 key 进行 md5 运算
    byte[] digest = md5(key);
    // 取 digest 数组的前四个字节进行 hash 运算,再将 hash 值传给 selectForKey 方法,
    // 寻找合适的 Invoker
    return selectForKey(hash(digest, 0));
}


private Invoker selectForKey(long hash) {
    // 到 TreeMap 中查找第一个节点值大于或等于当前 hash 的 Invoker
    Map.Entry> entry = virtualInvokers.tailMap(hash, true).firstEntry();
    // 如果 hash 大于 Invoker 在圆环上最大的位置,此时 entry = null,
    // 需要将 TreeMap 的头节点赋值给 entry
    if (entry == null) {
        entry = virtualInvokers.firstEntry();
    }


    // 返回 Invoker
    return entry.getValue();
}

如上,选择的过程相对比较简单了。首先是对参数进行 md5 以及 hash 运算,得到一个 hash 值。然后再拿这个值到 TreeMap 中查找目标 Invoker 即可。

到此关于 ConsistentHashLoadBalance 就分析完了。

在阅读ConsistentHashLoadBalance 源码之前,建议读者先补充背景知识,不然看懂代码逻辑会有很大难度。

05应用场景

DNS负载均衡最早的负载均衡技术是通过DNS来实现的,在DNS中为多个地址配置同一个名字,因而查询这个名字的客户机将得到其中一个地址,从而使得不同的客户访问不同的服务器,达到负载均衡的目的。DNS负载均衡是一种简单而有效的方法,但是它不能区分服务器的差异,也不能反映服务器的当前运行状态。

代理服务器负载均衡使用代理服务器,可以将请求转发给内部的服务器,使用这种加速模式显然可以提升静态网页的访问速度。然而,也可以考虑这样一种技术,使用代理服务器将请求均匀转发给多台服务器,从而达到负载均衡的目的。

地址转换网关负载均衡支持负载均衡的地址转换网关,可以将一个外部IP地址映射为多个内部IP地址,对每次TCP连接请求动态使用其中一个内部地址,达到负载均衡的目的。

协议内部支持负载均衡除了这三种负载均衡方式之外,有的协议内部支持与负载均衡相关的功能,例如HTTP协议中的重定向能力等,HTTP运行于TCP连接的最高层。

NAT负载均衡NAT(Network Address Translation网络地址转换)简单地说就是将一个IP地址转换为另一个IP地址,一般用于未经注册的内部地址与合法的、已获注册的Internet IP地址间进行转换。适用于解决Internet IP地址紧张、不想让网络外部知道内部网络结构等的场合下。

反向代理负载均衡普通代理方式是代理内部网络用户访问internet上服务器的连接请求,客户端必须指定代理服务器,并将本来要直接发送到internet上服务器的连接请求发送给代理服务器处理。反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器。反向代理负载均衡技术是把将来自internet上的连接请求以反向代理的方式动态地转发给内部网络上的多台服务器进行处理,从而达到负载均衡的目的。

混合型负载均衡在有些大型网络,由于多个服务器群内硬件设备、各自的规模、提供的服务等的差异,可以考虑给每个服务器群采用最合适的负载均衡方式,然后又在这多个服务器群间再一次负载均衡或群集起来以一个整体向外界提供服务(即把这多个服务器群当做一个新的服务器群),从而达到最佳的性能。将这种方式称之为混合型负载均衡。此种方式有时也用于单台均衡设备的性能不能满足大量连接请求的情况下。


审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 算法
    +关注

    关注

    23

    文章

    4455

    浏览量

    90756
  • 服务器
    +关注

    关注

    12

    文章

    8116

    浏览量

    82509
  • 负载均衡
    +关注

    关注

    0

    文章

    97

    浏览量

    12187
  • Dubbo
    +关注

    关注

    0

    文章

    17

    浏览量

    3134

原文标题:Dubbo负载均衡策略之 一致性哈希

文章出处:【微信号:OSC开源社区,微信公众号:OSC开源社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    相位一致性边缘检测

    大家起探讨相位一致性边缘检测,求指导
    发表于 06-11 13:38

    一致性测试

    谁有聚星公司射频一致性测试的程序啊,求个做参考,!
    发表于 07-14 18:11

    c6678cache一致性

    专家您好!    我现在在做6678 cache一致性的东西,想请问一下一致性的维护哪些是硬件实现的,哪些需要程序员实现?谢谢!
    发表于 06-24 04:38

    CAN一致性测试—容错测试

    CAN总线各节点质量的不一致引发的系统瘫痪、错误、死机等问题,CAN一致性测试已成为保证CAN网络安全运行的重要手段,本文将对CAN总线一致性测试中的容错测试进行介绍。CAN
    发表于 11-22 16:36

    LTE基站一致性测试的类别

    就LTE基站而言,RF测试方法与一致性要求至为关键,然而,调变格式、带宽、资源分配与移动导致选项复杂度增加,因此优化的一致性测试配置参数组合需求更为殷切。第三代合作伙伴项目(3GPP)长期演进计划
    发表于 06-06 06:41

    高速串行总线的物理层一致性测试是什么?由来呢?

    物理层的一致性测试作为近 10 多年来示波器最主要的用途之一直是产业界最常提到的名词之一。本文尝试将物理层一致性测试的含义,要素与目的及
    发表于 08-12 07:17

    一致性非锁定读分析

    MySQL探秘(六)InnoDB一致性非锁定读
    发表于 09-17 08:39

    MIPI一致性测试

    MIPI一致性测试测试项目:> TX测试;> RX测试;> S参数和阻抗测试;> DigRF,Unipro和LLI的测试;测试环境: MIPI测试对示波器带宽的要求 >
    发表于 09-26 13:31

    Infiniium一致性测试软件

    Infiniium 一致性测试软件
    发表于 10-28 17:28

    什么是霍尔元件的一致性

    什么是霍尔元件的一致性?霍尔开关元件主要是通过感应磁性来进行开关机,霍尔元件本身又属于无触点开关,因此具有感应距离。霍尔开关都有个触发值和释放值,触发值是指霍尔元件表面达到参数磁性大小,霍尔元器件
    发表于 10-12 09:34

    电池管理系统均衡及管理策略

    小的总会过充过放, 这就造成容量大的 衰减缓慢、寿命延长; 容量小的衰减加快, 寿命缩短, 两者之间的差异会越来越大。均衡的目的是缩小这种差异,保持电池的一致性。以下介绍了被动均衡和主动均 衡的原理并
    发表于 04-07 10:43

    为什么需要进行WiMAX协议一致性测试?

    为什么需要进行WiMAX协议一致性测试看完你就知道
    发表于 04-15 06:16

    如何确保蓝牙设计通过EMI一致性测试 ?

    选择蓝牙模块时需要考虑哪些因素?如何确保蓝牙设计通过EMI一致性测试 ?
    发表于 05-07 06:25

    如何实现信号电压幅值的一致性

    如何实现信号电压幅值的一致性
    发表于 05-20 07:23

    顺序一致性和TSO一致性分别是什么?SC和TSO到底哪个好?

    内存一致性之顺序一致性(sequential consistency)可以说,最直观的内存一致性模型是sequentially consistent(SC):内存访问执行的顺序与程序指定的顺序相同
    发表于 07-19 14:54