从源码角度搞懂 Ribbon 的负载策略

  • 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;

}

}

}

加权响应时间 - WeightedResponseTimeRule

=================================

WeightedResponseTimeRule 类继承了轮询策略类 RandomRule

初始化时,启动一个定时器,每隔 30s 根据服务的响应时间分配一次权重,响应时间越长,权重越低,被选择到的概率也越低。响应时间越短,权重越高,实例被选中概率越高。得到权重后,生成随机权重,命中权重比随机权重大的第一个服务实例。

public class WeightedResponseTimeRule extends RoundRobinRule {

// 每隔 30s 统计各服务权重

public static final int DEFAULT_TIMER_INTERVAL = 30 * 1000;

// 记录累计权重

private volatile List accumulatedWeights = new ArrayList();

// 初始化

void initialize(ILoadBalancer lb) {

if (serverWeightTimer != null) {

serverWeightTimer.cancel();

}

serverWeightTimer = new Timer(“NFLoadBalancer-serverWeightTimer-”

  • name, true);

// 统计各服务权重

serverWeightTimer.schedule(new DynamicServerWeightTask(), 0,

serverWeightTaskTimerInterval);

// do a initial run

ServerWeight sw = new ServerWeight();

sw.maintainWeights();

Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

public void run() {

logger

.info(“Stopping NFLoadBalancer-serverWeightTimer-”

  • name);

serverWeightTimer.cancel();

}

}));

}

@Override

public Server choose(ILoadBalancer lb, Object key) {

if (lb == null) {

return null;

}

Server server = null;

while (server == null) {

List currentWeights = accumulatedWeights;

// 判断线程是否中断

if (Thread.interrupted()) {

return null;

}

// 获取服务器列表

List allList = lb.getAllServers();

int serverCount = allList.size();

if (serverCount == 0) {

return null;

}

int serverIndex = 0;

// currentWeights.size() - 1 是所有权重的总和

double maxTotalWeight = currentWeights.size() == 0 ? 0 : currentWeights.get(currentWeights.size() - 1);

// 未命中任何服务器就调用轮询策略获取

if (maxTotalWeight < 0.001d || serverCount != currentWeights.size()) {

server = super.choose(getLoadBalancer(), key);

if(server == null) {

return server;

}

} else {

// 从 0 到 所有权重总和之间获取随机数作为随机权重

double randomWeight = random.nextDouble() * maxTotalWeight;

int n = 0;

// 命中权重比随机权重大的第一个服务实例

for (Double d : currentWeights) {

if (d >= randomWeight) {

serverIndex = n;

break;

} else {

n++;

}

}

server = allList.get(serverIndex);

}

if (server == null) {

/* Transient. */

Thread.yield();

continue;

}

if (server.isAlive()) {

return (server);

}

// Next.

server = null;

}

return server;

}

}

// 内部类

class ServerWeight {

public void maintainWeights() {

ILoadBalancer lb = getLoadBalancer();

if (lb == null) {

return;

}

if (!serverWeightAssignmentInProgress.compareAndSet(false, true)) {

return;

}

try {

logger.info(“Weight adjusting job started”);

AbstractLoadBalancer nlb = (AbstractLoadBalancer) lb;

LoadBalancerStats stats = nlb.getLoadBalancerStats();

if (stats == null) {

// no statistics, nothing to do

return;

}

double totalResponseTime = 0;

// 计算出所有服务实例累计的平均响应时间

for (Server server : nlb.getAllServers()) {

ServerStats ss = stats.getSingleServerStat(server);

totalResponseTime += ss.getResponseTimeAvg();

}

// 记录累计权重

Double weightSoFar = 0.0;

// 存放所有服务的权重

List finalWeights = new ArrayList();

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);

}

}

}

例如:现在有三个服务实例,平均响应时间分别为:

  • A:100ms

  • B:200ms

  • C:300ms

则权重分别是:

  • A:600-100 = 500

  • B:500+600-200 = 900

  • C:900+600-300 = 1200

生成的随机数若在 0-500 之间,则命中服务 A,如在 500 - 900 之间,则命中服务 B,如在 900 - 1200,则命中服务 C,如果没有命中任何服务实例,则取轮询策略的结果。

最佳可用策略 - BestAvailableRule

==========================

如未指定负载均衡器,采用轮询策略选取一个服务实例;

如指定了负载均衡器,逐个考察服务实例,过滤掉断路器跳闸状态的实例,从未过滤掉的实例中选择一个并发量最小的实例。如果未命中,则轮询策略选取一个服务实例。

public class BestAvailableRule extends ClientConfigEnabledRoundRobinRule {

@Override

public Server choose(Object key) {

// 未指定负载均衡器,调用轮询策略

if (loadBalancerStats == null) {

return super.choose(key);

}

// 获取所有服务器列表

List 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;

}

}

}

可用性过滤策略 - AvailabilityFilteringRule

===================================

该策略继承自抽象策略 PredicateBasedRule 类。

通过轮询的方式选取一个服务,如果不匹配过滤条件,则继续轮询10次,如果10次还未命中,就轮询选取一个实例。

过滤条件:断路器故障或者并发请求超过了设置的并发阈值

public class AvailabilityFilteringRule extends PredicateBasedRule {

@Override

public Server choose(Object key) {

int count = 0;

// 轮询策略选一个实例

Server server = roundRobinRule.choose(key);

while (count++ <= 10) {

// 判断是否符合断言条件

if (predicate.apply(new PredicateKey(server))) {

return server;

}

// 不满足断言条件再轮询选择一个实例

server = roundRobinRule.choose(key);

}

// 超过10次还不满足,使用 父类 PredicateBasedRule策略

return super.choose(key);

}

}

看看父类 PredicateBasedRule 的负载策略

public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {

@Override

public Server choose(Object key) {

ILoadBalancer lb = getLoadBalancer();

// 根据条件过滤后,采用轮询策略选取实例

Optional server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);

if (server.isPresent()) {

return server.get();

} else {

return null;

}

}

}

来看看上述中的断言条件是什么,进入到AvailabilityPredicate类查看断言条件

public class AvailabilityPredicate extends AbstractServerPredicate {

@Override

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

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

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

一线互联网大厂Java核心面试题库

image

正逢面试跳槽季,给大家整理了大厂问到的一些面试真题,由于文章长度限制,只给大家展示了部分题目,更多Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等已整理上传,感兴趣的朋友可以看看支持一波!

漫长,而且极易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-ZVUtPF9v-1710409774674)]
[外链图片转存中…(img-kjFHLGJ2-1710409774675)]
[外链图片转存中…(img-444vMMkc-1710409774675)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-u3JupgxZ-1710409774676)]

一线互联网大厂Java核心面试题库

[外链图片转存中…(img-LDJLz2gd-1710409774677)]

正逢面试跳槽季,给大家整理了大厂问到的一些面试真题,由于文章长度限制,只给大家展示了部分题目,更多Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等已整理上传,感兴趣的朋友可以看看支持一波!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 22
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值