Dubbo中的负载均衡算法之平滑加权轮询算法源码解析

news2024/11/24 13:31:10

Dubbo中的负载均衡算法之一致性哈希算法

哈希算法

假设这样一个场景,我们申请一台服务器来缓存100万的数据,这个时候是单机环境,所有的请求都命中到这台服务器。后来业务量上涨,我们的数据也从100万上升到了300万,原来的单机环境,无法容纳如此多的数据,所以我们决定扩容到三台服务器。但这个时候有一个问题,这三百万的数据如何平均分配到这三台服务器上呢?假设我们随机分配,那么当我们需要查询某一个缓存项时,则需要先去第一台机器进行查找,如果找到则返回给用户,如果没有,继续去第二台机器查找,如果还没有则继续去第三台机器查找,这样一来,查询缓存项的性能则会明显下降,有没有办法根据缓存项的键,直接定位到正确的机器进行查询呢?

一种行之有效的方法就是通过哈希算法去定位。对于相同的键,每次计算得出的哈希值应该是一致的。假设我们有三台机器,可以对键通过哈希算法得到哈希值,然后对3取余,结果只能是0、1或2,我们将这三个结果分别作为三台机器的编号。这样每次有查询缓存的请求过来,我们都可以按照以下步骤去做

  1. 对键进行哈希运算,得到一个哈希值,然后对3取余,得到一个机器编号
  2. 前往对应的机器进行查询。如果找到缓存项,直接返回结果;如果未找到,表示缓存项不存在。
    在这里插入图片描述

上述的方案看起来已经解决了问题,但是,如果在分布式场景中,却有着很大的局限性。因为在生产环境中,不可避免会发生以下几种情况:

  1. 服务器故障宕机,无法提供服务而下线
  2. 业务高峰期需要弹性扩容
  3. 业务低谷期需要弹性缩容

这几种场景都会导致后台服务器的数量 n 发生变化,那么我们原来的公式hash(key) % n因为n发生了变化,导致计算出的值也会发生变化,这样一来就会导致大量的缓存失效,造成缓存雪崩。如果是采用这种方案做负载均衡的话,也会导致大量的请求路由到错误的服务器,找不到对应的服务,导致服务异常。因此就有了一致性hash算法,用来解决这个问题,保证当后端移除或者添加一个服务器时,要尽可能小的改变存量请求与服务器之间的正确映射关系。

一致性哈希算法

基本介绍

一致性哈希算法最早是由David Karger等人在1997年发表的论文Consistent Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot Spots on the World Wide Web 中提出的。该算法的设计目标是为了解决因特网中的热点问题,即如何将数据尽可能均匀地分布到不同的节点上,以避免某些节点承受过大的负载。

它的主要思路是将整个哈希值空间组织成一个虚拟的圆环,让每个服务器节点在环上占据一个位置。有新的请求的时候,通过hash算法将请求映射到环上的一个位置,然后顺时针找到最近的服务器去进行处理。这样当一个服务器节点加入或退出时,只会影响到其附近的一小部分数据,而不会对整个系统造成很大的影响。他的一般步骤为:

  1. 如下图所示,我们虚拟出一个有232-1个节点的hash环,然后将服务器地址(ip+port)计算hash值,,对232-1取模,算出其在hash环上的位置,假设我们有三个服务器节点:S1、S2 、S3,其映射关系如图所示。
  2. 当一个新的服务请求到来时,也对该请求的标识符进行哈希计算,得到其在环上的位置。
  3. 从环上顺时针找到距离该请求最近的一个节点,将请求分配给该节点处理。例如请求R1,R2被分配给S1这个服务器去处理

在这里插入图片描述

如何解决服务器节点增加、减少带来的问题的

如果在hash环上加一个节点,例如我们添加了S4这个节点,那么原本应该路由到S1的请求和路由到S2的请求不会受到影响,依然可以正确路由,不过原本路由到S3的部分请求则将会被路由到新节点S4。这样就做到了我们前面说的尽可能小的改变已存在的服务请求与处理请求服务器之间的映射关系。

在这里插入图片描述

减少一个节点也是同样的道理,例如下图中我们下线了节点S3,那么原本应该路由到S1的请求和路由到S2的请求不会受到影响,依然可以正确路由,受影响的只是原本应该路由到S3的请求因为S3下线,而会路由到S1。

在这里插入图片描述

什么是虚拟节点

前面的图例中,我们可以看到S1,S2,S3三个节点是均匀分布在hash环上的,但是实际上,我们通过hash算法计算的时候可能达不到理想的效果,有可能会得到这样的分布效果。这就会导致大量的请求被路由到S3这个节点。因此提出了虚拟节点的概念。
在这里插入图片描述

所谓虚拟节点就是说并非真实的物理节点,而是对物理节点拷贝的一个副本,他们和其对应的真实物理节点有一样的IP和端口号,因此称为虚拟节点。如下图所示,我们有三个物理节点,S1、S2、 S3。然后对每个物理节点虚拟出了两个虚拟节点,将其分布在hash环上。这样假设有一个请求访问过来,通过hash计算落到了S3-2到S2-2的区间,那么他将会路由到S2-2这个服务器节点,而S2-2其实是S2的一个虚拟节点,所以实际处理这个请求的是S2这个物理节点。通过图我们可以直观的看到,引入虚拟节点后,请求的分布会进一步变得平衡,而引入的虚拟节点数量越多,则平衡性越好

在这里插入图片描述

Dubbo一致性哈希负载均衡的实现

dubbo一致性哈希负载均衡策略的实现在ConsistentHashLoadBalance这个类里,它继承了AbstractLoadBalance这个抽象类,实现了方法doSelect()方法,这个方法的作用是用来从多个服务提供者里面选择一个来调用。这里有两个重要的参数InvokerInvocationInvoker 我们可以理解为对服务提供者的一个封装,所有的服务提供者在dubbo里都会被转化为Invoker。所以List<Invoker<T>> invokers其实就是我们本次请求的所有服务提供者的列表。Invocation我们理解为对本次请求会话的一个封装,里面包括了请求的参数、方法以及变量

InvokerInvocation 的概念可参考:代码架构 | Apache Dubbo、实现细节 | Apache Dubbo

/**
 * 线程安全的一个map,缓存了所有的Selector(可以理解为一个选择器,用来从众多的服务提供者里面选择一个,实现负载均衡的效果),
 * key:服务提供者的签名
 * value:具体某一个服务对应的Selector
 */
private final ConcurrentMap<String, ConsistentHashSelector<?>> selectors = new ConcurrentHashMap<String, ConsistentHashSelector<?>>();

/**
 * 从invokers里选一个调用
 */
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
    String methodName = RpcUtils.getMethodName(invocation);

    //  1. 根据服务提供者的签名从前面提到的map里找对应的选择器
    String key = invokers.get(0).getUrl().getServiceKey() + "." + methodName;
    int invokersHashCode = invokers.hashCode();
    ConsistentHashSelector<T> selector = (ConsistentHashSelector<T>) selectors.get(key);

    // 1.1  没有找到?那么初始化,这是一个懒加载的过程
    if (selector == null || selector.identityHashCode != invokersHashCode) {
        selectors.put(key, new ConsistentHashSelector<T>(invokers, methodName, invokersHashCode));
        selector = (ConsistentHashSelector<T>) selectors.get(key);
    }

    // 1.2 通过selector选一个具体的服务提供者,处理请求
    return selector.select(invocation);
}

前面提到如果没有找打对应的Selector,那么我们要去做初始化,这里是如何初始化一个一致性哈希负载均衡策略的逻辑。

private static final class ConsistentHashSelector<T> {
	
    // 用TreeMap模拟一个hash环
    private final TreeMap<Long, Invoker<T>> virtualInvokers;

    private final int replicaNumber;

    private final int identityHashCode;

    private final int[] argumentIndex;

    // 初始化Selector的逻辑
    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);
        String[] index = 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]);
        }

        // 初始化hash环,将所有的invokers均匀的分布到环上
        for (Invoker<T> invoker : invokers) {
            String address = invoker.getUrl().getAddress();
            // 创建虚拟节点,每个物理机节点应该有replicaNumber个虚拟节点,但是这里用两次for循环,应该是为了加强hash的随机性
            for (int i = 0; i < replicaNumber / 4; i++) {
                byte[] digest = Bytes.getMD5(address + i);
                for (int h = 0; h < 4; h++) {
                    long m = hash(digest, h);
                    virtualInvokers.put(m, invoker);
                }
            }
        }
    }

    // 具体的选择逻辑,对请求计算md5值,算一个hash
    public Invoker<T> select(Invocation invocation) {
        byte[] digest = Bytes.getMD5(RpcUtils.getMethodName(invocation));
        return selectForKey(hash(digest, 0));
    }

    // 用ceilingEntry方法模拟从哈希环上选择顺时针最近的一个服务节点
    private Invoker<T> selectForKey(long hash) {
        Map.Entry<Long, Invoker<T>> entry = virtualInvokers.ceilingEntry(hash);
        if (entry == null) {
            entry = virtualInvokers.firstEntry();
        }
        return entry.getValue();
    }

    private long hash(byte[] digest, int number) {
        return (((long) (digest[3 + number * 4] & 0xFF) << 24)
                | ((long) (digest[2 + number * 4] & 0xFF) << 16)
                | ((long) (digest[1 + number * 4] & 0xFF) << 8)
                | (digest[number * 4] & 0xFF))
                & 0xFFFFFFFFL;
    }
}

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

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

相关文章

激光雷达和人工智能

几十年来&#xff0c;激光雷达一直是许多行业中非常有用的工具&#xff0c;但直到最近&#xff0c;随着人工智能&#xff08;AI&#xff09;解决方案的引入&#xff0c;我们才开始认识到它的真正潜力。激光雷达&#xff0c;又称光探测和测距&#xff0c;是一种遥感技术。它利用…

性能测试知多少----性能测试分类之我见

从这一篇开始&#xff0c;虫师向性能方面发力。翻看自己的博客&#xff0c;最早的时候热衷于jmeter&#xff0c;于是写了几篇图文并茂的文章&#xff08;其实&#xff0c;主要是操作截图加文字描述&#xff09;&#xff0c;之后&#xff0c;由于看到好多朋友关于性能的知识什么…

【RtpSeqNumOnlyRefFinder】webrtc m98: ManageFrameInternal 的帧决策过程分析

Jitterbuffer(FrameBuffer)需要组帧以后GOP内的参考关系 JeffreyLau 大神分析 了组帧原理而参考关系(RtpFrameReferenceFinder)的生成伴随了帧决策 FrameDecisionFrameDecision 影响力 帧的缓存。调用 OnAssembledFrame 传递已经拿到的RtpFrameObject 那么,RtpFrameObject…

出海营销必看:如何避免邮件被识别为垃圾邮件

对于现在的商业环境来说&#xff0c;邮件通信已经成为企业与客户、合作伙伴以及员工之间沟通和交流的重要方式。然而&#xff0c;尽管企业发送的邮件通常都是正常的、合规的&#xff0c;有时候却会被系统错误地标记为营销邮件。这个情况给企业带来了很多困扰。 如果企业的邮件…

软件测试工作的价值体现在哪里呢?

QA 的绩效如何考核&#xff1f;测试工作的价值体现在哪里&#xff1f; 这两个是大家比较关注&#xff0c;也是比较难的问题。确实&#xff0c;业务分析人员会产出需求文档&#xff0c;开发人员会产出软件&#xff0c;而 QA 的工作则很难定义明确的产出&#xff0c;很难被量化。…

服务号改订阅号怎么弄

服务号和订阅号有什么区别&#xff1f;服务号转为订阅号有哪些作用&#xff1f;很多小伙伴想把服务号改为订阅号&#xff0c;但是不知道改了之后具体有什么作用&#xff0c;今天跟大家具体讲解一下。首先我们知道服务号一个月只能发四次文章&#xff0c;但是订阅号每天都可以发…

Java通过cellstyle属性设置Excel单元格常用样式全面总结

最近做了一个导出Excel的功能&#xff0c;导出是个常规导出&#xff0c;但是拿来模板一看&#xff0c;有一些单元格的样式设置&#xff0c;包括合并&#xff0c;背景色&#xff0c;字体等等&#xff0c;毕竟不是常用的东西&#xff0c;需要查阅资料完成&#xff0c;但是搜遍全网…

小程序day01

简介: 小程序项目的基本结构 页面的组成部分 一个页面对应一个文件夹&#xff0c;所有有关的内容都放在一起。 JSON配置文件 2.app.json文件 3.project.config.json文件 4.sitemap.json文件 5.页面的.json配置文件 6. 新建小程序页面 7.修改项目首页 小程序代码构成 小程序的宿…

java 数据结构 ArrayList源码底层 LinkedList 底层源码 迭代器底层

文章目录 数据结构总结ArrayList源码底层LinkedList底层源码 迭代器底层 数据结构 对于数据结构我这边只告诉你右边框框里的 栈的特点:后进先出,先进后出,入栈也成为压栈,出栈也成为弹栈 栈就像一个弹夹 队列先进先出后进后出 队列像排队 链表查询满 但是增删快(相对于数组而…

【Unity ShaderGraph】| 快速制作一个 抖动效果

前言 【Unity ShaderGraph】| 快速制作一个 抖动效果一、效果展示二、UV抖动效果三、应用实例 前言 本文将使用ShaderGraph制作一个抖动效果&#xff0c;可以直接拿到项目中使用。对ShaderGraph还不了解的小伙伴可以参考这篇文章&#xff1a;【Unity ShaderGraph】| Shader Gr…

【Midjourney入门教程4】与AI对话,写好prompt的必会方法

文章目录 1、语法2、单词3、要学习prompt 框架4、善用参数&#xff08;注意版本&#xff09;5、善用模版6、临摹7、垫图 木匠不会因为电动工具的出现而被淘汰&#xff0c;反而善用工具的木匠&#xff0c;收入更高了。 想要驾驭好Midjourney&#xff0c;可以从以下方面出发调整&…

Excel自学三部曲_Part3:Excel工作场景实战(三)

文章目录 三、基础图表、透视图表制作与分析1. 条形图&#xff08;1&#xff09;给逾期金额加上条形图&#xff08;2&#xff09;各个城市的逾期发展趋势&#xff08;迷你图&#xff09; 2. 柱状图&#xff08;1&#xff09;同时展示每个城市上中下旬的逾期金额a. 格式设置&…

粉渐变网格背景孟菲斯风格工作总结PPT模板

这是一套蓝粉渐变网格背景孟菲斯风格工作总结PPT模板&#xff0c;共27页&#xff1b; PPT模板封面&#xff0c;使用了蓝粉渐变网格、圆点、几何图案背景图片。中间填写工作总结PPT标题。界面为孟菲斯风格。 PowerPoint模板内容页&#xff0c;由25张蓝色动态幻灯片图表&#x…

安防视频监控平台EasyCVR出现目录在线,通道离线的问题该如何解决?

视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安防视频监控的能…

全国产EtherCAT运动控制边缘控制器(六):RtBasic文件下载与连续轨迹加工的Python+Qt开发

今天&#xff0c;正运动小助手给大家分享一下全国产EtherCAT运动控制边缘控制器ZMC432H如何使用PythonQT实现连续轨迹加工。 01 功能简介 全国产EtherCAT运动控制边缘控制器ZMC432H是正运动的一款软硬件全国产自主可控&#xff0c;运动控制接口兼容EtherCAT总线和脉冲型的独立…

inquirer.js——交互式命令行用户界面

一、什么是inquirer.js 1、inquirer.js是一个开源的交互式命令行用户界面&#xff08;CLI&#xff09;库&#xff0c;可以让你轻松地与用户进行交互&#xff0c;获取用户输入并做出相应的处理。它的主要功能是提供了一系列常用的命令行交互界面组件&#xff0c;例如input、con…

C++命名空间概述

c的命名空间是为了解决重名的问题的&#xff0c;试想这样一个场景&#xff0c;我写了一个函数叫做copy()用来处理一些 can 数据的拷贝&#xff0c;但是在工程的其他位置有一个和我名字一模一样的函数是用来处理其他数据的拷贝&#xff0c;这个时候两个函数名就冲突了&#xff0…

项目资源不足,常见的5种处理方式

软件开发中&#xff0c;经常会遇到项目资源不足的情况&#xff0c;项目团队如果无法及时获得所需的人力、财力、物力等资源&#xff0c;往往会影响团队士气以及任务质量&#xff0c;造成无法按时完成任务&#xff0c;进而影响项目进度。 因此及时处理和应对资源不足的情况&…

宽带电力载波稳定吗?有丢数据吗?

随着我国智能电网建设的推进&#xff0c;宽带电力载波技术作为一种集电力传输与信息通信于一体的技术&#xff0c;得到了广泛关注。然而&#xff0c;宽带电力载波通信在实际应用过程中&#xff0c;稳定性问题及数据丢失现象成为制约其发展的瓶颈。为了进一步提高宽带电力载波的…