com.netflix.ribbon

ribbon-core

2.3.0

import com.netflix.client.IClientConfigAware;

import com.netflix.client.config.IClientConfig;

import com.netflix.loadbalancer.ILoadBalancer;

import com.netflix.loadbalancer.IRule;

import com.netflix.loadbalancer.Server;

public class AbstractLoadBalancerRule implements IRule, IClientConfigAware {

private ILoadBalancer lb;

@Override

public void initWithNiwsConfig(IClientConfig clientConfig) {

}

@Override

public Server choose(Object key) {

return null;

}

@Override

public void setLoadBalancer(ILoadBalancer lb) {

this.lb = lb;

}

@Override

public ILoadBalancer getLoadBalancer() {

return lb;

}

}

4.3.2 RandomRule

该策略实现了从服务实例清单中随机选择个服务实例的功能。它的具体实现如下,可以看到IRule接口的choose 0bject key)函数实现,委托给了该类中的choose(ILoadBalancer lb,object key),该方法增加了一个负载均衡器对象的参数。

从具体的实现上看,它会使用传入的负载均衡器来获得可用实例列表upList和所有实例列表 allList,并通过rand.nextInt (serverCount)函数来获取一个随机数,并将该随机数作为upList的索引值来返回具体实例。

同时,具体的选择逻辑在一个while(server == null)循环之内,而根据选择逻辑的实现,正常情况下每次选择都应该选出一个服务实例,如果出现死循环获取不到服务实例时,则很有可能存在并发的Bug。

import com.netflix.client.IClientConfigAware;

import com.netflix.client.config.IClientConfig;

import com.netflix.loadbalancer.ILoadBalancer;

import com.netflix.loadbalancer.IRule;

import com.netflix.loadbalancer.Server;

import java.util.List;

import java.util.Random;

public class RandomRule implements IRule, IClientConfigAware {

private ILoadBalancer lb;

@Override

public void initWithNiwsConfig(IClientConfig clientConfig) {

}

@Override

public Server choose(Object key) {

return choose(getLoadBalancer(), key);

}

public Server choose(ILoadBalancer lb, Object key) {

Server server = null;

while (server == null) {

if (Thread.interrupted()) {

return null;

}

List upList = lb.getReachableServers();

List allList = lb.getAllServers();

int serverCount = allList.size();

if (serverCount == 0) {

return null;

}

Random random = new Random();

int index = random.nextInt(serverCount);

server = upList.get(index);

if (server == null) {

Thread.yield();

continue;

}

if (server.isAlive()) {

return server;

}

server = null;

Thread.yield();

}

return server;

}

@Override

public void setLoadBalancer(ILoadBalancer lb) {

this.lb = lb;

}

@Override

public ILoadBalancer getLoadBalancer() {

return lb;

}

}

4.3.3 RoundRobinRule

该策略实现了按照线性轮询的方式依次选择每个服务实例的功能。它的具体实现如下,其详细结构与RandomRule非常类似。除了循环条件不同外,就是从可用列表中获取所谓的逻辑不同。从循环条件中,我们可以看到增加了一个count计数变量,该变量会在每次循环之后累加,也就是说,如果一直选择不到server超过10次,那么就会结束尝试,并打印一个警告信息No available alive servers after 10 tries from load balancer: …。

import com.netflix.client.IClientConfigAware;

import com.netflix.client.config.IClientConfig;

import com.netflix.loadbalancer.ILoadBalancer;

import com.netflix.loadbalancer.IRule;

import com.netflix.loadbalancer.Server;

import lombok.extern.slf4j.Slf4j;

import java.util.List;

@Slf4j

public class RoundRobinRule implements IRule, IClientConfigAware {

private ILoadBalancer lb;

private AtomicInteger nextServerCyclicCounter;

public RoundRobinRule() {

nextServerCyclicCounter = new AtomicInteger(0);

}

public RoundRobinRule(ILoadBalancer lb) {

this();

setLoadBalancer(lb);

}

@Override

public void initWithNiwsConfig(IClientConfig clientConfig) {

}

@Override

public Server choose(Object key) {

return choose(getLoadBalancer(), key);

}

public Server choose(ILoadBalancer lb, Object key) {

Server server = null;

int count = 0;

while (server == null && count++ < 10) {

List reachableServers = lb.getReachableServers();

List allServers = lb.getAllServers();

int upCount = reachableServers.size();

int serverCount = allServers.size();

if (upCount == 0 || serverCount == 0) {

log.warn(“No available alive servers after 10 tries 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;

}

@Override

public void setLoadBalancer(ILoadBalancer lb) {

this.lb = lb;

}

@Override

public ILoadBalancer getLoadBalancer() {

return lb;

}

private int incrementAndGetModulo(int modulo) {

int current;

int next;

do {

current = this.nextServerCyclicCounter.get(); //nextServerCyclicCounter是AtomicInteger对象,默认值0,可保证线程安全性

next = (current + 1) % modulo; //每次往后移一位,取集合中的下一个server。这里要注意的是从1开始,即数组中的第二个server会被第一个调用。

} while (!this.nextServerCyclicCounter.compareAndSet(current, next)); //操作完成后用CAS操作将next赋值给nextServerCyclicCounter

return next;

}

}

4.3.4 RetryRule

该策略实现了一个具备重试机制的实例选择功能。从下面的实现中我们可以看到,在其内部还定义了一个IRule对象,默认使用了RoundRobinRule实例。而在choose方法中则实现了对内部定义的策略进行反复尝试的策略,若期间能够选择到具体的服务实例就返回,若选择不到就根据设置的尝试结束时间为阈值(maxRetryMillis参数定义的值+ choose方法开始执行的时间戳),当超过该阈值后就返回null。

import com.netflix.loadbalancer.ILoadBalancer;

import com.netflix.loadbalancer.IRule;

import com.netflix.loadbalancer.InterruptTask;

import com.netflix.loadbalancer.Server;

public class RetryRule extends AbstractLoadBalancerRule {

IRule iRule = new RoundRobinRule();

long maxRetryMillis = 500;

@Override

public Server choose(Object key) {

return choose(getLoadBalancer(), key);

}

public Server choose(ILoadBalancer lb, Object key) {

long requestTime = System.currentTimeMillis();

long deadline = requestTime + maxRetryMillis;

Server answer = null;

answer = iRule.choose(key);

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

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

while (!Thread.interrupted()) {

answer = iRule.choose(key);

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

Thread.yield();

} else {

break;

}

}

task.cancel();

}

if (answer == null || !answer.isAlive()) {

return null;

} else {

return answer;

}

}

}

4.3.4 WeightedResponseTimeRule

该策略是对RoundRobinRule 的扩展,增加了根据实例的运行情况来计算权重,并根据权重来挑选实例,以达到更优的分配效果,它的实现主要有三个核心内容。

4.3.4.1 定时任务

weightedResponseTimeRule策略在初始化的时候会通过serverweightTimer.schedule (new DynamicServerWeightTask(),0,serverWeightTaskTimerInterval)启动一个定时任务,用来为每个服务实例计算权重,该任务默认30秒执行一次。

4.3.4.2 权重计算

在源码中我们可以轻松找到用于存储权重的对象 List accumulated-Weights = new arrayList( ),该List中每个权重值所处的位置对应了负载均衡器维护的服务实例清单中所有实例在清单中的位置。

维护实例权重的计算过程通过maintainweights函数实现,具体如下面的代码所示:

import com.netflix.client.IClientConfigAware;

import com.netflix.client.config.IClientConfig;

import com.netflix.loadbalancer.*;

import lombok.extern.slf4j.Slf4j;

import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;

import java.util.ArrayList;

import java.util.List;

import java.util.Random;

import java.util.concurrent.atomic.AtomicBoolean;

@Slf4j

public class WeightedResponseTimeRule implements IRule, IClientConfigAware {

private ILoadBalancer lb;

private volatile List accumulatedWeights = new ArrayList();

protected AtomicBoolean serverWeightAssignmentInProgress = new AtomicBoolean(false);

@Override

public void initWithNiwsConfig(IClientConfig clientConfig) {

}

@Override

public Server choose(Object key) {

return null;

}

@Override

public void setLoadBalancer(ILoadBalancer lb) {

this.lb = lb;

}

@Override

public ILoadBalancer getLoadBalancer() {

return lb;

}

public void maintainWeights() {

ILoadBalancer lb = getLoadBalancer();

try {

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

AbstractLoadBalancer nlb = (AbstractLoadBalancer) lb;

LoadBalancerStats stats = nlb.getLoadBalancerStats();

//计算所有实例的平均相应时间总和:totalResponseTime

double totalResponseTime = 0;

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

//如果服务实例的状态快照不在缓存中,那么这里会进行自动加载

ServerStats ss = stats.getSingleServerStat(server);

totalResponseTime += ss.getResponseTimeAvg();

}

//逐个计算每个实例的权重:weightSoFar+totalResponseTime-实例的平均响应

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 (Throwable t) {

log.error(“Exception while dynamically calculating server weights”, t);

} finally {

serverWeightAssignmentInProgress.set(false);

}

}

void setWeights(List weights) {

this.accumulatedWeights = weights;

}

该函数的实现主要分为两个步骤:

根据 LoadBalancerstats 中记录的每个实例的统计信息,累加所有实例的平均响应时间,得到总平均响应时间totalResponseTime,该值会用于后续的计算。 为负载均衡器中维护的实例清单逐个计算权重(从第一个开始),计算规则为weightsoFar+totalResponseTime一实例的平均响应时间,其中weightSoFar初始化为零,并且每计算好一个权重需要累加到weightsoFar上供下一次计算使用。

举个简单的例子来理解这个计算过程,假设有4个实例A、B、C、D,它们的平均响应时间为10、40、80、100,所以总响应时间是10+40+80+100=230,每个实例的权重为总响应时间与实例自身的平均响应时间的差的累积所得,所以实例A、B、C、D的权重分别如下所示。

实例A:230- 10 =220 实例B: 220+(230-40)=410 实例C:410+(230- 80)= 560 实例D:560+(230- 100)= 690

需要注意的是,这里的权重值只是表示了各实例权重区间的上限,并非某个实例的优先级,所以不是数值越大被选中的概率就越大。

那么什么是权重区间呢?

以上面例子的计算结果为例,它实际上是为这4个实例构建了4个不同的区间,每个实例的区间下限是上一个实例的区间上限,而每个实例的区间上限则是我们上面计算并存储于Listaccumulatedweights 中的权重值,其中第一个实例的下限默认为零。所以,根据上面示例的权重计算结果,我们可以得到每个实例的权重区间。

实例A:[0,220] 实例B:(220,410] 实例C:(410,560] 实例D:(560,690)

不难发现,实际上每个区间的宽度就是:总的平均响应时间–实例的平均响应时间,所以实例的平均响应时间越短、权重区间的宽度越大,而权重区间的宽度越大被选中的概率就越高。

这些区间边界的开闭是如何确定的呢?为什么不那么规则?下面我们会通过实例选择算法的解读来解释。

4.3.4.3 实例选择

weightedResponseTimeRule选择实例的实现与之前介绍的算法结构类似,选择实例的核心过程就两步:

生成一个[0,最大权重值)区间内的随机数。 遍历权重列表,比较权重值与随机数的大小,如果权重值大于等于随机数,就拿当前权重列表的索引值去服务实例列表中获取具体的实例。

这就是在上一节中提到的服务实例会根据权重区间挑选的原理,而权重区间边界的开闭原则根据算法,正常每个区间为(x,y]的形式,但是第一个实例和最后一个实例为什么不同呢?由于随机数的最小取值可以为0,所以第一个实例的下限是闭区间,同时随机数的最大值取不到最大权重值,所以最后一个实例的上限是开区间。

若继续以上面的数据为例进行服务实例的选择,则该方法会从[ 0,690)区间中选出一个随机数,比如选出的随机数为230,由于该值位于第二个区间,所以此时就会选择实例B来进行请求。、

若继续以上面的数据为例进行服务实例的选择,则该方法会从[0,690)区间中选出一个随机数,比如选出的随机数为230,由于该值位于第二个区间,所以此时就会选择实例B来进行请求。

4.3.5 ClientConfigEnabledRoundRobinRule

该策略较为特殊,我们一般不直接使用它。因为它本身并没有实现什么特殊的处理逻辑,正如下面的源码所示,在它的内部定义了一个RolundRobinRule策略,而choose函数的实现也正是使用了RoundRobinRule的线性轮询机制,所以它实现的功能实际上与RoundRobinRule相同

那么定义它有什么特殊的用处呢?

虽然我们不会直接使用该策略,但是通过继承该策略,默认的choose就实现了线性轮询机制,在子类中做一些高级策略时通常有可能会存在一些无法实施的情况,那么就可以用父类的实现作为备选。在后文中我们将继续介绍的高级策略均是基于ClientConfigEnabledRoundRobinRule的扩展。

4.3.6 BestAvailableRule

该策略继承自clientconfigEnabledRoundRobinRule,在实现中它注入了负载均衡器的统计对象LoadBalancerStats,同时在具体的 choose 算法中利用LoadBalancerStats保存的实例统计信息来选择满足要求的实例。

通过遍历负载均衡器中维护的所有服务实例会过滤掉故障的实例,并找出并发请求数最小的一个,所以该策略的特性是可选出最空闲的实例。

4.3.7 PredicateBasedRule

这是一个抽象策略,它也继承了clientconfigEnabledRoundRobinRule,从其命名中可以猜出这是一个基于 Predicate 实现的策略,Predicate 是Google GuavaCollection工具对集合进行过滤的条件接口。

4.3.8 ZoneAvoidanceRule

该策略我们在介绍负载均衡器ZoneAwareLoadBalancer 时已经提到过,它也是PredicateBasedRule的具体实现类。在之前的介绍中主要针对ZoneAvoidanceRule中用于选择Zone区域策略的一些静态函数,比如createSnapshot、getAvailableZones。

在这里我们将详细看看ZoneAvoidanceRule作为服务实例过滤条件的实现原理。

它使用了CompositePredicate来进行服务实例清单的过滤。这是一个组合过滤条件,在其构造函数中,它以ZoneAvoidancePredicate为主过滤条件,AvailabilityPredicate为次过滤条件初始化了组合过滤条件的实例。

4.4 配置详解

4.4.1 自动化配置

由于Ribbon中定义的每一个接口都有多种不同的策略实现,同时这些接口之间又有一定的依赖关系,这便使得第一次 使用Ribbon的开发者很难上手,不知道如何选择具体的实现策略以及如何组织他们之间的关系。spring Cloud Ribbon中的自动化配置恰恰能够解决这样的痛点,在引入Spring Cloud Ribbon的依赖之后,就能够自动化构建下面这些接口的实现。

IClientConfig: Ribbon 的客户端配置,默认采用com.netflix.client.config. DefaultclientConfigImpl 实现。 IRule: Ribbon的负载均衡策略,默认采用com.netflix.loadbalancer.ZoneAvoidanceRule 实现,该策略能够在多区域环境下选出最佳区域的实例进行访问。 IPing:Ribbon的实例检查策略,默认采用com.netflix.loadbalancer.NoOpPing实现,该检查策略是一个特殊的实现,实际上它并不会检查实例是否可用,而是始终返回true,默认认为所有服务实例都是可用的 ServerList:服务实例清单的维护机制,默认采用com.netflix.loadbalancer.ConfigurationBasedServerList实现。 ServerListFilter :服务实例清单过滤机制,默认采用org.springframework.cloud.netflix.ribbon.ZonePreferenceServerListFilter实现,该策略能够优先过滤出与请求调用方处于同区域的服务实例 ILoadBalancer:负载均衡器,默认采用com.netflix.loadbalancer.ZoneAwareLoadBalancer实现,它具备了区域感知的能力。

注意 :上面这些自动化配置内容仅在没有引入Spring Cloud Eureka等服务时才会如此配置,在同时引入 Eureka和 Ribbon依赖时,自动化配置会有一些不同,后续我会做详细的介绍。

比如下面的配置内容,由于创建了PingUrl实例,所以默认的NoOpPing就不会被创建。

@Configuration

public class MyRibbonConfiguration {

@Bean

public IPing ribbonPing(IClientConfig clientConfig){

return new PingUrl();

}

}

另外,也可以通过使用@Ribbonclient注解来实现更细粒度的客户端配置,比如下面的代码实现了为hello-service服务使用MyRibbonConfiguration中的配置

org.springframework.cloud

spring-cloud-netflix-ribbon

2.2.6.RELEASE

@Configuration

@RibbonClient(name = “hello-service”, configuration = MyRibbonConfiguration.class)

public class RibbonConfiguration {

}

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

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

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

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

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

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

总结

上述知识点,囊括了目前互联网企业的主流应用技术以及能让你成为“香饽饽”的高级架构知识,每个笔记里面几乎都带有实战内容。

很多人担心学了容易忘,这里教你一个方法,那就是重复学习。

打个比方,假如你正在学习 spring 注解,突然发现了一个注解@Aspect,不知道干什么用的,你可能会去查看源码或者通过博客学习,花了半小时终于弄懂了,下次又看到@Aspect 了,你有点郁闷了,上次好像在哪哪哪学习,你快速打开网页花了五分钟又学会了。

从半小时和五分钟的对比中可以发现多学一次就离真正掌握知识又近了一步。

人的本性就是容易遗忘,只有不断加深印象、重复学习才能真正掌握,所以很多书我都是推荐大家多看几遍。哪有那么多天才,他只是比你多看了几遍书。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取! 图片转存中…(img-QZH07h0N-1713327738120)]

[外链图片转存中…(img-oizXg8fx-1713327738121)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

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

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

总结

上述知识点,囊括了目前互联网企业的主流应用技术以及能让你成为“香饽饽”的高级架构知识,每个笔记里面几乎都带有实战内容。

很多人担心学了容易忘,这里教你一个方法,那就是重复学习。

打个比方,假如你正在学习 spring 注解,突然发现了一个注解@Aspect,不知道干什么用的,你可能会去查看源码或者通过博客学习,花了半小时终于弄懂了,下次又看到@Aspect 了,你有点郁闷了,上次好像在哪哪哪学习,你快速打开网页花了五分钟又学会了。

从半小时和五分钟的对比中可以发现多学一次就离真正掌握知识又近了一步。

[外链图片转存中…(img-PL67QUAe-1713327738121)]

人的本性就是容易遗忘,只有不断加深印象、重复学习才能真正掌握,所以很多书我都是推荐大家多看几遍。哪有那么多天才,他只是比你多看了几遍书。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

好文链接

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。