当前位置: 首页 > news >正文

【云原生微服务八】Ribbon负载均衡策略之WeightedResponseTimeRule源码剖析(响应时间加权)

文章目录

  • 一、前言
  • 二、WeightedResponseTimeRule
    • 1、计算权重?
      • 1)如何更新权重?
      • 2)如何计算权重?
      • 3)例证权重的计算
    • 2、权重的使用
      • 1)权重区间问题?

一、前言

前置Ribbon相关文章:

  1. 【云原生&微服务一】SpringCloud之Ribbon实现负载均衡详细案例(集成Eureka、Ribbon)
  2. 【云原生&微服务二】SpringCloud之Ribbon自定义负载均衡策略(含Ribbon核心API)
  3. 【云原生&微服务三】SpringCloud之Ribbon是这样实现负载均衡的(源码剖析@LoadBalanced原理)
  4. 【云原生&微服务四】SpringCloud之Ribbon和Erueka集成的细节全在这了(源码剖析)
  5. 【微服务五】Ribbon随机负载均衡算法如何实现的
  6. 【微服务六】Ribbon负载均衡策略之轮询(RoundRobinRule)、重试(RetryRule)
  7. 【微服务七】Ribbon负载均衡策略之BestAvailableRule

我们聊了以下问题:

  1. 为什么给RestTemplate类上加上了@LoadBalanced注解就可以使用Ribbon的负载均衡?
  2. SpringCloud是如何集成Ribbon的?
  3. Ribbon如何作用到RestTemplate上的?
  4. 如何获取到Ribbon的ILoadBalancer?
  5. ZoneAwareLoadBalancer(属于ribbon)如何与eureka整合,通过eureka client获取到对应注册表
  6. ZoneAwareLoadBalancer如何持续从Eureka中获取最新的注册表信息
  7. 如何根据负载均衡器ILoadBalancer从Eureka Client获取到的List<Server>中选出一个Server?
  8. Ribbon如何发送网络HTTP请求?
  9. Ribbon如何用IPing机制动态检查服务实例是否存活?
  10. Ribbon负载均衡策略之随机(RandomRule)、轮询(RoundRobinRule)、重试(RetryRule)、选择并发量最小的(BestAvailableRule)实现方式;

本文继续讨论 根据响应时间加权算法(WeightedResponseTimeRule)是如何实现的?

二、WeightedResponseTimeRule

WeightedResponseTimeRule继承自RoundRobinRule,也就是说该策略是对RoundRobinRule的扩展,其增加了 根据实例运行情况来计算权重 并根据权重挑选实例的规则,以达到更优的负载、实例分配效果。

下面我们一点点来看WeightedResponseTimeRule是如何实现根据相应时间计算权重并根据权重挑选实例的?

1、计算权重?

WeightedResponseTimeRule在初始化的时候会初始化父类RoundRobinRule,在RoundRobinRule的有参构造函数中会调用setLoadBalancer(ILoadBalancer)方法,WeightedResponseTimeRule类中重写了setLoadBalancer(ILoadBalancer)方法,在setLoadBalancer(ILoadBalancer)中会调用initialize(ILoadBalancer)对权重进行初始化、并定时更新。
在这里插入图片描述

public static final int DEFAULT_TIMER_INTERVAL = 30 * 1000;

private int serverWeightTaskTimerInterval = DEFAULT_TIMER_INTERVAL;

1)如何更新权重?

WeightedResponseTimeRule通过Timer#schedule()方法启动一个上一个任务结束到下一个任务开始之间间隔30s执行一次的定时任务为每个服务实例计算权重;
在这里插入图片描述
定时任务的主体是DynamicServerWeightTask

// WeightedResponseTimeRule的内部类
class DynamicServerWeightTask extends TimerTask {
    public void run() {
        ServerWeight serverWeight = new ServerWeight();
        try {
            serverWeight.maintainWeights();
        } catch (Exception e) {
            logger.error("Error running DynamicServerWeightTask for {}", name, e);
        }
    }
}

DynamicServerWeightTask的run()方法中会实例化一个ServerWeight对象,并通过其maintainWeights()方法计算权重。

2)如何计算权重?

无论是权重的初始化还是权重的定时更新,都是使用ServerWeight#maintainWeights()方法来计算权重:

// WeightedResponseTimeRule的内部类
class ServerWeight {

    public void maintainWeights() {
        ILoadBalancer lb = getLoadBalancer();
        if (lb == null) {
            return;
        }
        // CAS保证只有一个线程可以进行权重的计算操作
        if (!serverWeightAssignmentInProgress.compareAndSet(false,  true))  {
            return; 
        }
        
        try {
            logger.info("Weight adjusting job started");
            AbstractLoadBalancer nlb = (AbstractLoadBalancer) lb;
            LoadBalancerStats stats = nlb.getLoadBalancerStats();
            if (stats == null) {
                return;
            }
            // 所有实例的平均响应时间总和
            double totalResponseTime = 0;
            for (Server server : nlb.getAllServers()) {
                // 汇总每个实例的平均响应时间到totalResponseTime上
                ServerStats ss = stats.getSingleServerStat(server);
                totalResponseTime += ss.getResponseTimeAvg();
            }
            // 计算每个实例的权重:weightSoFar + totalResponseTime - 实例的平均响应时间
            // 实例的平均响应时间越长、权重就越小,就越不容易被选择到
            Double weightSoFar = 0.0;
            
            List<Double> finalWeights = new ArrayList<Double>();
            for (Server server : nlb.getAllServers()) {
                ServerStats ss = stats.getSingleServerStat(server);
                double weight = totalResponseTime - ss.getResponseTimeAvg();
                weightSoFar += weight;
                finalWeights.add(weightSoFar);   
            }
            setWeights(finalWeights);
        } catch (Exception e) {
            logger.error("Error calculating server weights", e);
        } finally {
            // 表示权重计算结束,允许其他线程进行权重计算
            serverWeightAssignmentInProgress.set(false);
        }

    }
}

方法的核心逻辑:

  1. LoadBalancerStats中记录了每个实例的统计信息,累加所有实例的平均响应时间,得到总平均响应时间totalResponseTime
  2. 为负载均衡器中维护的实例列表逐个计算权重(从第一个开始),计算规则为:weightSoFar + totalResponseTime - 实例的平均响应时间;
  3. 其中weightSoFar初始化为零,并且每计算好一个权重需要累加到weightSoFar上供下一次计算使用;

3)例证权重的计算

举个例子,假如服务A有四个实例:A、B、C、D,他们的平均响应时间(单位:ms)为:10、50、100、200。

  • 服务A的所有实例的总响应时间(totalResponseTime)为:10 + 50 + 100 + 200 = 360
  • 每个实例的权重计算规则为:总响应时间(totalResponseTime) 减去 实例的平均响应时间 + 累加的权重weightSoFar,具体到每个实例的计算如下:
  1. 实例A:360 - 10 + 0 = 350(weightSoFar = 0)
  2. 实例B:360 - 50 + 350 = 660(weightSoFar = 350)
  3. 实例C:360 - 100 + 660 = 920(weightSoFar = 660)
  4. 实例D:360 - 200 + 920 = 1080(weightSoFar = 920)

这里的权重值表示各实例权重区间的上限,以上面的计算结果为例,它为这4个实例各构建了一个区间:

  1. 每个实例的区间下限是上一个实例的区间上限;
  2. 每个实例的区间上限是我们计算出的并存储于在List<Double>类型的accumulatedWeights变量中的权重值,其中第一个实例的下限默认为零。

所以,根据上面示例的权重计算结果,我们可以得到每个实例的权重区间:

  1. 实例A:[0,350](weightSoFar = 0)
  2. 实例B:(350, 660](weightSoFar = 350)
  3. 实例C:(660, 920](weightSoFar = 660)
  4. 实例D:(920, 1080](weightSoFar = 920)

从这里我们可以确定每个区间的宽度实际就是:总的平均响应时间 - 实例的平均响应时间,所以服务实例的平均响应时间越短、权重区间的宽度就越大,服务实例被选中的概率就越高。

这些区间边界的开闭如何确定?区间在哪里使用?

2、权重的使用

我们知道Ribbon负载均衡算法体现在IRule的choose(Object key)方法中,而choose(Object key)方法中又会调用choose(ILoadBalancer lb, Object key)方法,所以我们只需要看WeightedResponseTimeRule的choose(ILoadBalancer lb, Object key)方法:
在这里插入图片描述

方法的核心流程如下:

  1. 如果服务实例的最大权重值 < 0.001 或者服务的实例个数发生变更,则采用父类RoundRobinRule做轮询负载;
  2. 否则,利用Random函数生成一个随机数randomWeight,然后遍历权重列表,找到第一个权重值大于等于随机数randomWeight的列表索引下标,然后拿当前权重列表的索引值去服务实例列表中获取具体实例。

1)权重区间问题?

正常每个区间都为(x, y],但是第一个实例和最后一个实例不同:

  1. 由于随机数的最小取值可以为0,所以第一个实例的下限是闭区间;
  2. 随机数的最大值取不到最大权重值,所以最后一个实例的上限是开区间;

最后

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

小编已加密:aHR0cHM6Ly9kb2NzLnFxLmNvbS9kb2MvRFVrVm9aSGxQZUVsTlkwUnc==出于安全原因,我们把网站通过base64编码了,大家可以通过base64解码把网址获取下来。

相关文章:

  • 内网渗透之内网信息收集(六)
  • 深入理解高级数据结构之红黑树
  • 流量回放-The Big Picture
  • 【Git总结大全】git操作从入门到实战(总结篇)
  • 【ansible第三次作业】
  • postgres源码解析 缓冲池管理器--1
  • 干货| 算法工程师常见问题(基础算法篇)
  • 机器学习笔记之高斯混合模型(四)EM算法求解高斯混合模型(M步操作)
  • Jupyter 介绍
  • Code For Better 谷歌开发者之声——Google Play
  • 【好书推荐】程序是怎样跑起来的
  • 关于技术分享及内卷
  • 源码解析Java数组如何选择排序的算法
  • java计算机毕业设计基于安卓Android微信小程序的共享单车租赁系统uniApp
  • TCP 的自然律
  • python3.6+scrapy+mysql 爬虫实战
  • [NodeJS] 关于Buffer
  • [译] React v16.8: 含有Hooks的版本
  • 【vuex入门系列02】mutation接收单个参数和多个参数
  • 5、React组件事件详解
  • C++类的相互关联
  • gitlab-ci配置详解(一)
  • Java 内存分配及垃圾回收机制初探
  • Python_OOP
  • Python利用正则抓取网页内容保存到本地
  • vuex 笔记整理
  • Vue官网教程学习过程中值得记录的一些事情
  • 彻底搞懂浏览器Event-loop
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 区块链技术特点之去中心化特性
  • 如何解决微信端直接跳WAP端
  • 微信小程序--------语音识别(前端自己也能玩)
  • 问:在指定的JSON数据中(最外层是数组)根据指定条件拿到匹配到的结果
  • 正则表达式
  • 中文输入法与React文本输入框的问题与解决方案
  • 仓管云——企业云erp功能有哪些?
  • ​水经微图Web1.5.0版即将上线
  • #传输# #传输数据判断#
  • $ is not function   和JQUERY 命名 冲突的解说 Jquer问题 (
  • (笔试题)分解质因式
  • (附源码)springboot金融新闻信息服务系统 毕业设计651450
  • (附源码)ssm高校实验室 毕业设计 800008
  • (转)C#开发微信门户及应用(1)--开始使用微信接口
  • (转)C语言家族扩展收藏 (转)C语言家族扩展
  • (转)iOS字体
  • (转)mysql使用Navicat 导出和导入数据库
  • *setTimeout实现text输入在用户停顿时才调用事件!*
  • .Mobi域名介绍
  • .net core控制台应用程序初识
  • .NET 动态调用WebService + WSE + UsernameToken
  • .NET 同步与异步 之 原子操作和自旋锁(Interlocked、SpinLock)(九)
  • .NET/C# 获取一个正在运行的进程的命令行参数
  • .net遍历html中全部的中文,ASP.NET中遍历页面的所有button控件
  • .net反混淆脱壳工具de4dot的使用
  • .Net中的集合