Ribbon 负载均衡策略 —— 图解、源码级解析

news2024/11/9 4:57:50

文章目录

  • 负载均衡策略
    • RandomRule
    • RoundRobinRule
    • RetryRule
    • WeightedResponseTimeRule
    • BestAvailableRule
    • AvailabilityFilteringRule
    • ZoneAvoidanceRule
  • Ribbon 负载均衡策略源码
    • RandomRule源码
    • RoundRobinRule源码
    • BestAvailableRule源码
    • RetryRule源码

通过本文你可以学习到:

  1. 常见的7种负载均衡策略思想
  2. 自旋锁的使用方式
  3. 防御性编程

负载均衡策略

RandomRule

该策略会从当前可用的服务节点中,随机挑选一个节点访问,使用了yield+自旋的方式做重试,还采用了严格的防御性编程。


RoundRobinRule

该策略会从一个节点一步一步地向后选取节点,如下图所示:
在这里插入图片描述
在多线程环境下,两个请求同时访问这个Rule也不会读取到相同节点:这靠的是RandomRobinRule底层的自旋锁+CAS的同步操作。

CAS+自旋锁这套组合技是高并发下最廉价的线程安全手段,因为这套操作不需要锁定系统资源。但缺点是,自旋锁如果迟迟不能释放,将会带来CPU资源的浪费,因为自旋本身并不会执行任何业务逻辑,而是单纯的使CPU空转。所以通常情况下会对自旋锁的旋转次数做一个限制,比如JDK中synchronize底层的锁升级策略,就对自旋次数做了动态调整。

while (true) {
    // cas操作
    if (cas(expected, update)) {
        // 业务逻辑代码
        // break或退出return
    }
}

Eureka为了防止服务下线被重复调用,就使用AtomicBoolean的CAS方法做同步控制;

奈飞提供的SpringCloud组件有特别多用到CAS的地方,感兴趣的小伙伴们可以发现一下


RetryRule

RetryRule是一个类似装饰器模式的规则,装饰器相当于一层套一层的套娃,每一层都会加上一层独特的功能。

经典的装饰器模式示意图:
在这里插入图片描述
借助上面的思路,RetryRule就是给其他负载均衡策略加上重试功能。在RetryRule里还藏着一个subRule,这才是真正被执行的负载均衡策略,RetryRule正是要为它添加重试功能(如果初始化时没指定subRule,将默认使用RoundRibinRule)。


WeightedResponseTimeRule

这个规则继承自RoundRibbonRule,他会根据服务节点的响应时间计算权重,响应时间越长权重就越低,响应越快则权重越高,权重的高低决定了机器被选中概率的高低。也就是说,响应时间越小的机器,被选中的概率越大。

服务器刚启动的时候,对各个服务节点采样不足,因此会采用轮询策略,当积累到一定的样本时候,才会切换到WeightedResponseTimeRule模式。


BestAvailableRule

过滤掉故障服务以后,它会基于过去30分钟的统计结果选取当前并发量最小的服务节点作为目标地址。如果统计结果尚未生成,则采用轮询的方式选定节点。


AvailabilityFilteringRule

这个规则底层依赖RandomRobinRule来选取节点,但必须要满足它的最低要求的节点才会被选中。如果节点满足了要求,无论其响应时间或者当前并发量是什么,都会被选中。

每次AvailabilityFilteringRule都会请求RobinRule挑选一个节点,然后对这个节点做以下两步检查:

  1. 是否处于熔断状态
  2. 节点当前的请求连接数超过阈值,超过了则表示节点目前太忙

如果被选中的server挂了,那么AFR会自动重试(最多10次),让RobinRule重新选择一个服务节点


ZoneAvoidanceRule

这个过滤器包含了组合过滤条件,分别是Zone级别和可用性级别。
在这里插入图片描述

  • Zone Filter: Zone可以理解为机房所属的大区域,这里会对这个Zone下面所有的服务节点进行健康情况过滤。

  • 可用性过滤: 这里和AvailabilityFilteringRule的验证过程很像,会过滤掉当前并发量较大,或者处于熔断状态的服务节点。


Ribbon 负载均衡策略源码

RandomRule源码

先从RandomRule看起,核心的方法是:
请添加图片描述

public Server choose(ILoadBalancer lb, Object key) {
    if (lb == null) {
        return null;
    }
    Server server = null;

    while (server == null) {
        if (Thread.interrupted()) {
            return null;
        }
        List<Server> upList = lb.getReachableServers();
        List<Server> allList = lb.getAllServers();

        int serverCount = allList.size();
        if (serverCount == 0) {
            /*
             * No servers. End regardless of pass, because subsequent passes
             * only get more restrictive.
             */
            return null;
        }

        int index = chooseRandomInt(serverCount);
        server = upList.get(index);

        if (server == null) {
            Thread.yield();
            continue;
        }

        if (server.isAlive()) {
            return (server);
        }

        server = null;
        Thread.yield();
    }

    return server;

}

RandomRule里方法的入参key没有用到,所以可以先暂时忽略


while循环逻辑是如果server为空,则找到一个可用的server

if (Thread.interrupted()) {
    return null;
}

如果线程暂停了,则直接返回空(防御性编程)


List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();

allList存储的是所有的服务,upList存储的是可运行状态的服务


int serverCount = allList.size();
if (serverCount == 0) {
    return null;
}

服务中心上没有server注册,则返回空


int index = chooseRandomInt(serverCount);
server = upList.get(index);

随机选择一个server


其中,chooseRandomInt的逻辑如下:

protected int chooseRandomInt(int serverCount) {
    return ThreadLocalRandom.current().nextInt(serverCount);
}

返回0到serverCount中间的任意一个值

java中的随机是可以预测到结果的,真随机数一般会掺杂一些不可预测的数据,比如当前cpu的温度


回到RandomRulechoose方法:

如果发现随机选择的server为空表示此时serverList正在被修正,此时让出线程资源,进行下一次循环,对应最开始的防御性编程

if (server == null) {
    Thread.yield();
    continue;
}
if (server.isAlive()) {
    return (server);
}

如果server可用直接return


server = null;
Thread.yield();

如果不可用则server置为空,下一次循环会选一个新的,最后让出资源。

所以该方法每次进入下一次循环时都会让出线程。


RoundRobinRule源码

接下来看RoundRobinRule

public Server choose(ILoadBalancer lb, Object key) {
    if (lb == null) {
        log.warn("no load balancer");
        return null;
    }

    Server server = null;
    int count = 0;
    while (server == null && count++ < 10) {
        List<Server> reachableServers = lb.getReachableServers();
        List<Server> allServers = lb.getAllServers();
        int upCount = reachableServers.size();
        int serverCount = allServers.size();

        if ((upCount == 0) || (serverCount == 0)) {
            log.warn("No up servers available from load balancer: " + lb);
            return null;
        }

        int nextServerIndex = incrementAndGetModulo(serverCount);
        server = allServers.get(nextServerIndex);

        if (server == null) {
            Thread.yield();
            continue;
        }

        if (server.isAlive() && (server.isReadyToServe())) {
            return (server);
        }

        server = null;
    }

    if (count >= 10) {
        log.warn("No available alive servers after 10 tries from load balancer: "
                + lb);
    }
    return server;
}

while循环里面有一个计数器,如果重试10次依然没有结果返回就不重试了。


List<Server> reachableServers = lb.getReachableServers();
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();

reachableServers就是up状态的server


if ((upCount == 0) || (serverCount == 0)) {
    log.warn("No up servers available from load balancer: " + lb);
    return null;
}

没有可用服务器则返回空


int nextServerIndex = incrementAndGetModulo(serverCount);
server = allServers.get(nextServerIndex);

选择哪个下标的server,进入incrementAndGetModulo方法


private int incrementAndGetModulo(int modulo) {
    for (;;) {
        int current = nextServerCyclicCounter.get();
        int next = (current + 1) % modulo;
        if (nextServerCyclicCounter.compareAndSet(current, next))
            return next;
    }
}

使用了自旋锁,nextServerCyclicCounter是一个线程安全的数字。


if (server == null) {
    Thread.yield();
    continue;
}

如果获取到的server为空则让出资源,继续下一次循环


if (server.isAlive() && (server.isReadyToServe())) {
    return (server);
}

server是正常的则返回


server = null;

最后没有让出线程资源,因为重试10次后就退出循环了


BestAvailableRule源码

接下来看BestAvailableRule

@Override
public Server choose(Object key) {
    if (loadBalancerStats == null) {
        return super.choose(key);
    }
    List<Server> serverList = getLoadBalancer().getAllServers();
    int minimalConcurrentConnections = Integer.MAX_VALUE;
    long currentTime = System.currentTimeMillis();
    Server chosen = null;
    for (Server server: serverList) {
        ServerStats serverStats = loadBalancerStats.getSingleServerStat(server);
        if (!serverStats.isCircuitBreakerTripped(currentTime)) {
            int concurrentConnections = serverStats.getActiveRequestsCount(currentTime);
            if (concurrentConnections < minimalConcurrentConnections) {
                minimalConcurrentConnections = concurrentConnections;
                chosen = server;
            }
        }
    }
    if (chosen == null) {
        return super.choose(key);
    } else {
        return chosen;
    }
}

if (loadBalancerStats == null) {
    return super.choose(key);
}

如果loadBalancerStats为空则调用父类的choose方法,父类方法直接委托给RoundRobinRule来完成choose


for循环里先从loadBalancerStats中获取到当前服务的状态

ServerStats serverStats = loadBalancerStats.getSingleServerStat(server);
public ServerStats getSingleServerStat(Server server) {
    return getServerStats(server);
}
protected ServerStats getServerStats(Server server) {
    try {
        return serverStatsCache.get(server);
    } catch (ExecutionException e) {
        ServerStats stats = createServerStats(server);
        serverStatsCache.asMap().putIfAbsent(server, stats);
        return serverStatsCache.asMap().get(server);
    }
}

这里是从缓存中获取server的stats,如果获取失败则默认创建一个stats并添加到缓存中,然后从cache中再获取一次。


随后判断是否处于熔断状态

if (!serverStats.isCircuitBreakerTripped(currentTime)) {...}
public boolean isCircuitBreakerTripped(long currentTime) {
    long circuitBreakerTimeout = getCircuitBreakerTimeout();
    if (circuitBreakerTimeout <= 0) {
        return false;
    }
    return circuitBreakerTimeout > currentTime;
}

首先获得熔断的TimeOut(表示截止到未来某个时间熔断终止),如果大于当前时间说明处于熔断状态。


熔断的TimeOut由下面方法计算得到:

private long getCircuitBreakerTimeout() {
    long blackOutPeriod = getCircuitBreakerBlackoutPeriod();
    if (blackOutPeriod <= 0) {
        return 0;
    }
    return lastConnectionFailedTimestamp + blackOutPeriod;
}

返回上一次连接失败的时间戳 + blackOutPeriod

其中又调用了

private long getCircuitBreakerBlackoutPeriod() {
    int failureCount = successiveConnectionFailureCount.get();
    int threshold = connectionFailureThreshold.get();
    if (failureCount < threshold) {
        return 0;
    }
    int diff = (failureCount - threshold) > 16 ? 16 : (failureCount - threshold);
    int blackOutSeconds = (1 << diff) * circuitTrippedTimeoutFactor.get();
    if (blackOutSeconds > maxCircuitTrippedTimeout.get()) {
        blackOutSeconds = maxCircuitTrippedTimeout.get();
    }
    return blackOutSeconds * 1000L;
}

failureCount是失败的个数,从一个计数器里获得,阈值从一个缓存的属性中获得,之后计算两个的差值,再根据缓存中的一些属性计算最终的秒数,最后乘以1000返回。


回到BestAvailableRulechoose方法,只有不处于熔断状态才能继续走后面的流程

if (concurrentConnections < minimalConcurrentConnections) {
    minimalConcurrentConnections = concurrentConnections;
    chosen = server;
}

选出连接数最小的服务器


if (chosen == null) {
    return super.choose(key);
} else {
    return chosen;
}

最后返回


核心是找到一个最轻松的服务器。


RetryRule源码

查看RetryRule源码:

public Server choose(ILoadBalancer lb, Object key) {
   long requestTime = System.currentTimeMillis();
   long deadline = requestTime + maxRetryMillis;

   Server answer = null;

   answer = subRule.choose(key);

   if (((answer == null) || (!answer.isAlive()))
         && (System.currentTimeMillis() < deadline)) {

      InterruptTask task = new InterruptTask(deadline
            - System.currentTimeMillis());

      while (!Thread.interrupted()) {
         answer = subRule.choose(key);

         if (((answer == null) || (!answer.isAlive()))
               && (System.currentTimeMillis() < deadline)) {
            /* pause and retry hoping it's transient */
            Thread.yield();
         } else {
            break;
         }
      }

      task.cancel();
   }

   if ((answer == null) || (!answer.isAlive())) {
      return null;
   } else {
      return answer;
   }
}
long requestTime = System.currentTimeMillis();
long deadline = requestTime + maxRetryMillis;

先记录当前时间和deadline,在截止时间之前可以一直重试。


answer = subRule.choose(key);

方法里面是由subRule来实现具体的负载均衡逻辑,这里默认类型是RoundRobinRule


如果选到的是空或者选到的不是up的,且时间在ddl之前则进入重试逻辑:

while (!Thread.interrupted()) {
   answer = subRule.choose(key);

   if (((answer == null) || (!answer.isAlive()))
         && (System.currentTimeMillis() < deadline)) {
      /* pause and retry hoping it's transient */
      Thread.yield();
   } else {
      break;
   }
}

如果线程中断了就中断重试。之后重新选择服务器,如果又没选到则把资源让出去,下一次while循环再选,在while循环之前会起一个任务

InterruptTask task = new InterruptTask(deadline - System.currentTimeMillis());

到了截止时间之后,程序会中断重试的流程

task.cancel();

最后返回

if ((answer == null) || (!answer.isAlive())) {
   return null;
} else {
   return answer;
}

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

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

相关文章

自学大语言模型之BERT

BERT 模型由 Jacob Devlin、Ming-Wei Chang、Kenton Lee 和 Kristina Toutanova在BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding中提出。它是一种双向变换器&#xff0c;使用掩码语言建模目标和对包含多伦多图书语料库和维基百科的大型语…

开源高星精选,10个2023企业级Python测试项目,再不学习时间就没了

纸上得来终觉浅&#xff0c;光学习理论知识是不够的。 想要学好软件测试必须要结合实战项目深入掌握&#xff0c;今天给大家分享十个2022最新企业级Python软件测试项目&#xff1a; ​ 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; ▌Rank 1&#xf…

SEW-Movifit软件的调试步骤

首先安装软件&#xff08;名称和版本为SEW_Software_MotionStudio_V5-9-0-4-compact&#xff09;。安装完毕后打开软件&#xff0c;新建一个工程。 3、新建完成之后会进入如下画面。 4、点击红框内的图标进行设置 5、打开后会显示如下画面&#xff0c;在下拉菜单中选择serial这…

【Unity3D】高斯模糊特效

1 高斯模糊原理 边缘检测特效中使用了卷积运算进行了边缘检测&#xff0c;本文实现的高斯模糊特效同样使用了卷积运算&#xff0c;关于卷积核和卷积运算的概念&#xff0c;读者可以参考边缘检测特效。 本文完整资源见→Unity3D高斯模糊特效。 我们将用于模糊处理的卷积核称为模…

C++模拟牛顿力学(2D)

简介 如何用计算机来模拟真实世界呢&#xff1f;计算机最大的功能是计算&#xff0c;而物理学的种种公式就把现实世界中的物理规律以数学的语言描绘了出来&#xff0c;从而使我们可以通过计算大致模拟现实世界的物体运动。因此不难想到把物理学定律&#xff08;这里用的是牛顿…

SAP-MM-维护物料主数据的类(Class)和特性(Characteristic)

一&#xff0e;说明 物料主数据有千个左右条目&#xff0c;但仍不能满足各类物料自有特性的描述&#xff0c;为此SAP启用了类&#xff08;Class&#xff09;和特性&#xff08;Characteristic&#xff09;&#xff0c;并在物料主数据的分类视图&#xff08;Characteristic&…

推荐一款免费开源的代码质量分析工具

文章目录 一、简介二、环境安装三、使用说明四、其他报错UnicodeDecodeError: ‘ascii’ codec can’t decode byte 0xe6 in position 29: ordinal not in range(128)**linux:****windos:** 五、安全编程规范 一、简介 Flawfinder是一款开源的关于C/C静态扫描分析工具&#xf…

C++入门——关键字|命名空间|输入输出

前言&#xff1a; 今天我们又开启了一个崭新的大门——C面向对象编程语言&#xff0c;C是怎么来的呢&#xff1f;答案是&#xff1a;因为C语言的有很多不足&#xff0c;我们的祖师爷用着不爽&#xff0c;就不断更改&#xff0c;就改出来了一门新的语言&#xff0c;C。C语言兼容…

黑客常用的十大工具(附工具安装包),你知道几款?

注&#xff1a;本文总结白帽黑客常用的十大工具。文档仅供参考&#xff0c;不得用于非法用途&#xff0c;否则后果自负。 1 Nmap nmap是一个网络连接端扫描软件&#xff0c;用来扫描网上电脑开放的网络连接端。确定哪些服务运行在哪些连接端&#xff0c;并且推断计算机运行哪个…

谈谈开源的利弊和国内的开源 ——《新程序员005:开源深度指南 新金融背后的科技力量》书评

感谢CSDN的送测 《新程序员005&#xff1a;开源深度指南 & 新金融背后的科技力量》 是一本以计算机编程和金融科技为主题的杂志书&#xff0c;由中国最大的开源社区之一的开源社主办&#xff0c;内容丰富多样&#xff0c;包括了众多知名开源项目和工具的介绍&#xff0c;同…

第 3 章:使用 Vue 脚手架

目录 具体步骤 模板项目的结构&#xff08;脚手架文件结构&#xff09; Vue脚手架报错 修改方案&#xff1a; 关于不同版本的Vue vue.config.js配置文件 ref属性 props配置项 mixin(混入) 插件 小结&#xff1a; scoped样式 小结&#xff1a; Todo-list 案例 小结…

kafka重点问题解答-----kafka 的设计架构

1. kafka 都有哪些特点&#xff1f; 高吞吐量&#xff0c;低延迟 可以热扩展 并发度高 具有容错性(挂的只剩1台也能正常跑) 可靠性高 2. 请简述你在哪些场景下会选择 kafka&#xff1f; kafka的一些应用 日志收集&#xff1a;一个公司可以用kafka可以收集各种服务的log&…

自学黑客(网络安全/web渗透),一般人我还是劝你算了吧

由于我之前写了不少网络安全技术相关的文章&#xff0c;不少读者朋友知道我是从事网络安全相关的工作&#xff0c;于是经常有人私信问我&#xff1a; 我刚入门网络安全&#xff0c;该怎么学&#xff1f; 要学哪些东西&#xff1f; 有哪些方向&#xff1f; 怎么选&a…

chatgpt赋能python:Python分解三位数:打造高效的数学学习工具

Python分解三位数&#xff1a;打造高效的数学学习工具 介绍 Python是一种动态、解释型、高级编程语言&#xff0c;广泛应用于数据分析、人工智能、机器学习等领域。在数学教育中&#xff0c;Python也是一个非常好的工具&#xff0c;可以帮助学生更好地理解数学知识和提高解题…

Linux内核模块开发 第 6 章

The Linux Kernel Module Programming Guide Peter Jay Salzman, Michael Burian, Ori Pomerantz, Bob Mottram, Jim Huang译 断水客&#xff08;WaterCutter&#xff09; 6 字符设备驱动 include/linux/fs.h 中定义了结构体 file_operations &#xff0c;这个结构体包含指…

深度学习论文分享(三)Look More but Care Less in Video Recognition(NIPS2022)

深度学习论文分享&#xff08;三&#xff09;Look More but Care Less in Video Recognition&#xff08;NIPS2022&#xff09; 前言Abstract1. Introduction2 Related Work2.1 Video Recognition2.2 Redundancy in Data&#xff08;数据冗余&#xff09; 3 Methodology3.1 Arc…

Apache 虚拟主机企业应用

企业真实环境中&#xff0c; 一台服务器发布单个网站非常浪费资源&#xff0c;所以一台 web 服务器上会发布多个网站少则2~3个多则 30多个网站 在一台服务器上发布多网站&#xff0c;也称之为部署多个虚拟主机&#xff0c; Web 虚拟主机配置方法有以下 种&#xff1a; 1、基于单…

基于机器学习的内容推荐算法及其心理学、社会学影响闲谈

基于机器学习的内容推荐算法目前在各类内容类APP中使用的非常普遍。在购物、时尚、新闻咨询、学习等领域&#xff0c;根据用户的喜好&#xff0c;进行较为精准的用户画像与内容推荐。此类算法不但可以较为准确的分析用户的特征&#xff0c;如年龄、性别等&#xff0c;还能通过长…

QT项目实战(视频播放器)

文章目录 前言一、QMediaPlayer二、QVideoWidget三、QAudioOutput四、播放器代码实现五、最终效果总结 前言 本篇文章将使用QT6.4来实现一个简单视频播放器&#xff0c;在QT中使用一个视频播放器还是非常简单的。那么下面就让我们一起来实现这个视频播放器吧。 一、QMediaPla…

深度学习应用篇-计算机视觉-图像增广[1]:数据增广、图像混叠、图像剪裁类变化类等详解

【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化算法、卷积模型、序列模型、预训练模型、对抗神经网络等 专栏详细介绍&#xff1a;【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化…