Dubbo负载均衡策略之 一致性哈希 | 京东云技术团队

news2024/11/19 7:32:56

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

一、负载均衡

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

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

二、哈希算法

图1 无哈希算法请求

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

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

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

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

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

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

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

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

图3 增加一台服务器

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

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

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

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

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

三、一致性哈希算法

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

图4 一致性哈希算法

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

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

图5 数据倾斜

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

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

加入虚拟节点。

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

图6 引入虚拟节点

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

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

四、一致性哈希在DUBBO 中的应用

图7 Dubbo中一致性哈希环

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

图8 数据倾斜问题

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

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

public class ConsistentHashLoadBalance extends AbstractLoadBalance {


    private final ConcurrentMap<String, ConsistentHashSelector<?>> selectors = 
        new ConcurrentHashMap<String, ConsistentHashSelector<?>>();


    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> 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<T> selector = (ConsistentHashSelector<T>) selectors.get(key);
        // 如果 invokers 是一个新的 List 对象,意味着服务提供者数量发生了变化,可能新增也可能减少了。
        // 此时 selector.identityHashCode != identityHashCode 条件成立
        if (selector == null || selector.identityHashCode != identityHashCode) {
            // 创建新的 ConsistentHashSelector
            selectors.put(key, new ConsistentHashSelector<T>(invokers, methodName, identityHashCode));
            selector = (ConsistentHashSelector<T>) selectors.get(key);
        }


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

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

private static final class ConsistentHashSelector<T> {


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


    private final int replicaNumber;


    private final int identityHashCode;


    private final int[] argumentIndex;


    ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) {
        this.virtualInvokers = new TreeMap<Long, Invoker<T>>();
        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<T> 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<T> 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<T> selectForKey(long hash) {
    // 到 TreeMap 中查找第一个节点值大于或等于当前 hash 的 Invoker
    Map.Entry<Long, Invoker<T>> 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 源码之前,建议读者先补充背景知识,不然看懂代码逻辑会有很大难度。

五、应用场景

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

作者:京东物流 乔杰

来源:京东云开发者社区

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/641416.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32单片机(五)第二节:EXTI外部中断练习(对射式红外传感器计次和旋转编码器计次)

❤️ 专栏简介&#xff1a;本专栏记录了从零学习单片机的过程&#xff0c;其中包括51单片机和STM32单片机两部分&#xff1b;建议先学习51单片机&#xff0c;其是STM32等高级单片机的基础&#xff1b;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 &#xff1a;适用于想要…

深入学习 Linux 操作系统的存储 IO 堆栈

一、Linux 存储堆栈图 当使用 read() 和 write() 系统调用向内核提交读写 I/O 请求操作时需要经历的步骤&#xff1a; 1&#xff09;首先&#xff0c;请求经过虚拟文件系统&#xff0c;虚拟文件系统提供了统一的文件和文件系统的相关接口&#xff0c;屏蔽了不同文件系统的差异…

Applied soft computing期刊投稿流程

爱斯唯尔账户注册问题: 账号所属作者即默认通讯作者 刚开始应该会进入这个页面,这里的用户名和密码一般是导师(通讯作者)的账号密码,可以询问老师,然后点击author login登陆就好。 注册里面强调一点,这里面的Title 一般是职称的意思,如果是学生可以填Mr.或者Mrs. 投…

Python 网络舆情分析系统,舆论可视化界面

1 简介 舆情管理系统&#xff0c;这不仅仅可以帮助当地的管理人员迅速的排查跟本地有关的负面言论&#xff0c;还可以避免网民因为本身意识不到位而评论或发布一些不好的观点的情况&#xff0c;最终的目的就是帮助社会更好的发展。 2 技术栈 说明技术栈备注后台Python前端HT…

多线程:Lamda表达式

Lamda表达式 希腊字母表中第十一个字母&#xff0c;英文名字为lambda避免匿名内部类定义过多其实质是函数类编程的概念new Thread (() -->System.out.println(“多线程的学习…”)).start();为什么要使用Lamda表达式&#xff1f; 1、避免匿名内部类定义过多 2、可以让你的代…

Vue中如何进行移动端适配与响应式布局?

Vue中如何进行移动端适配与响应式布局&#xff1f; 如今&#xff0c;移动端适配与响应式布局已经成为Web开发中不可或缺的一部分。Vue.js作为一款流行的JavaScript框架&#xff0c;也提供了许多有用的工具和技术来实现移动端适配和响应式布局。在这篇文章中&#xff0c;我们将…

阿里云服务器ESSD PL-0云盘与ESSD PL-1云盘区别及选择参考

在我们选购阿里云服务器的时候&#xff0c;通常系统盘与数据盘类型都是ESSD云盘&#xff0c;而云盘的性能又分为PL-0和PL-1&#xff0c;虽然都属于ESSD云盘&#xff0c;但是它们之间的性能是有区别的&#xff0c;收费标准也不一样&#xff0c;本文为大家介绍一下阿里云服务器ES…

26岁,大学肄业,家里蹲5年,这位失业青年的求救,牵动百万网友的心……

近期&#xff0c;知乎上有一个求救贴火了&#xff0c;浏览量一度飙升到480多万&#xff0c;近千名网友在帖子下留言&#xff0c;纷纷表达自己的看法。 帖子是这样的。题主26岁&#xff0c;大学肄业。最长只上过12天的班&#xff0c;每次出门面试找工作就会非常害怕&#xff0c…

win11 idea shift+F6快捷键失效问题

1.结论 win11微软输入发最新版问题&#xff0c;使用以前版本即可 2.排查问题过程 最近一直没用shiftF6&#xff0c;当今天要使用时候&#xff0c;突然发现失效了&#xff0c;开始了以下排查过程 2.1 软件快捷键冲突 刚开始以为和其他软件或diea内部快捷键冲突&#xff0c;做了…

计算机组成原理 | 深入理解ELF格式和静态链接

深入解析C语言代码到机器码的过程 #mermaid-svg-UhCa4aLgwtwtM4hS {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-UhCa4aLgwtwtM4hS .error-icon{fill:#552222;}#mermaid-svg-UhCa4aLgwtwtM4hS .error-text{fill:#5…

whatsapp如何开发国外客户

WhatsApp可以说是目前2023年最火的社交营销方式之一&#xff0c;目前全球活跃人数大概在10亿&#xff0c;每日发送消息超过650亿条&#xff0c;语音和通话的时长超过20亿分钟&#xff0c;全球三分之一的人在用WhatsApp的&#xff0c;所有做外贸&#xff0c;WhatsApp是必不可少的…

ESXi 7.0 U3m Fujitsu (富士通) 定制版 OEM Custom Installer CD

VMware ESXi 7.0 Update 3m - 领先的裸机 Hypervisor (All OEM Customized Installer CDs) ESXi 7.0 U3m Standard (标准版) ESXi 7.0 U3m Dell (戴尔) 定制版 OEM Custom Installer CD ESXi 7.0 U3m HPE (慧与) 定制版 OEM Custom Installer CD ESXi 7.0 U3m Lenovo (联想) 定…

AIGC + RTE,一个实时互动的应用实践

一夜之间&#xff0c;区块链、元宇宙、Web3、VR等风口似乎都消散了&#xff0c;似乎只有 AI 才是C位。声网 RTC 云市场各类插件 AIGC&#xff0c;会迸发出什么样的火花呢&#xff1f; 最近&#xff0c;声网内部组织了一次黑客松&#xff0c;经过激烈的角逐&#xff0c;我们团…

【华为OD机试真题2023B卷 JAVAJS】完全二叉树非叶子部分后序遍历

华为OD2023(B卷)机试题库全覆盖,刷题指南点这里 完全二叉树非叶子部分后序遍历 知识点数组树递归 时间限制:1s 空间限制:256MB 限定语言:不限 题目描述: 给定一个以顺序储存结构存储整数值的完全二叉树序列(最多1000个整数),请找出此完全二叉树的所有非叶子节点部分,…

成功邀请媒体采访的关键步骤,媒介易助你成为媒体热门

在企业品牌推广和宣传中&#xff0c;与媒体建立合作关系&#xff0c;并邀请媒体进行采访是非常重要的环节。通过媒体的报道和宣传&#xff0c;企业可以扩大品牌的曝光度&#xff0c;提升品牌形象和知名度。然而&#xff0c;成功的媒体邀约并非易事&#xff0c;需要一定的策略和…

启动QT Linux应用程序后黑屏闪烁?

启动QT Linux应用程序后黑屏闪烁&#xff1f; 提问 问 4 年&#xff0c; 3 个月 前 修改于 3 个月前 点击833次 1 我将嵌入式Linux&#xff08;imx6q&#xff09;与eglfs一起使用。 帧缓冲在启动Qt应用程序时正在绘制黑屏。 简单的应用程序是黑色的1秒下。重度施用4~5秒。 这仅…

如何关闭A770显卡的灯效

A770的灯效看起来是不错&#xff0c;但对于我这样的实用主义者来说&#xff0c;他没啥用。所以想着怎么样能把他给关了。 查了一下&#xff0c;intel光放给出的办法如下&#xff1a; 如何使用英特尔 RGB 控制器配置英特尔 Arc™ A770 显卡有限版卡上 LED 的外观和行为。 英特…

java读取文件内容

直接上代码&#xff0c;两个类&#xff1a;一个工具类&#xff0c;一个测试类 工具类代码&#xff1a; package org.example.study.util;import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils;import java.io.*; import java.nio.charset.Charset…

FMC子卡设计资料原理图:FMC210-1路1Gsps AD、1路2.5Gsps DA的FMC子卡

FMC210-1路1Gsps AD、1路2.5Gsps DA的FMC子卡 一、板卡概述 FMC-1AD2DA是我司自主研发的一款1路1G AD采集、1路2.5G DA回放的FMC子卡。板卡采用标准FMC子卡架构&#xff0c;可方便的与其他FMC板卡实现高速互联&#xff0c;可广泛用于高频模拟信号采集、雷达系统测试等场…

【ChatGPT-工具篇 2】采用预置命令进行Prompt的输入

采用预置命令向ChatGPT输入命令 案例如下&#xff1a; {"ai_tutor":{"Author":"Andy.L","name":"JavaTutor","version":"1.0","features":{"personalization":{"depth"…