【云原生&微服务八】Ribbon负载均衡策略之WeightedResponseTimeRule源码剖析(响应时间加权)
  Xa9NVipNa2Vn 2023年11月02日 43 0


文章目录

一、前言

前置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)​​对权重进行初始化、并定时更新。

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

public static final int DEFAULT_TIMER_INTERVAL = 30 * 1000;

private int serverWeightTaskTimerInterval = DEFAULT_TIMER_INTERVAL;

1)如何更新权重?

WeightedResponseTimeRule通过Timer#schedule()方法启动一个上一个任务结束到下一个任务开始之间间隔30s执行一次的定时任务为每个服务实例计算权重;

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


定时任务的主体是​​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:​​230 - 10 + 0 = 220​​(weightSoFar = 0)
  2. 实例B:​​230 - 50 + 220 = 400​​(weightSoFar = 220)
  3. 实例C:​​230 - 100 + 400 = 530​​(weightSoFar = 400)
  4. 实例D:​​230 - 200 + 530 = 560​​(weightSoFar = 530)

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

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

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

  1. 实例A:​​[0,220]​​(weightSoFar = 0)
  2. 实例B:​​(220, 400]​​(weightSoFar = 220)
  3. 实例C:​​(400, 530]​​(weightSoFar = 400)
  4. 实例D:​​(530, 560]​​(weightSoFar = 530)

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

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

2、权重的使用

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

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

方法的核心流程如下:

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

1)权重区间问题?

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

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


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
Xa9NVipNa2Vn