SpringCloud04—客户端负载均衡 SpringCLoud Ribbon

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

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 {

}

最后:学习总结——MyBtis知识脑图(纯手绘xmind文档)

学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。下方即为我手绘的MyBtis知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的MyBtis知识脑图原件(包括上方的面试解析xmind文档)

image

除此之外,前文所提及的Alibaba珍藏版mybatis手写文档以及一本小小的MyBatis源码分析文档——《MyBatis源码分析》等等相关的学习笔记文档,也皆可分享给认可的朋友!

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!
服务使用MyRibbonConfiguration中的配置

org.springframework.cloud

spring-cloud-netflix-ribbon

2.2.6.RELEASE

@Configuration

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

public class RibbonConfiguration {

}

最后:学习总结——MyBtis知识脑图(纯手绘xmind文档)

学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。下方即为我手绘的MyBtis知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的MyBtis知识脑图原件(包括上方的面试解析xmind文档)

[外链图片转存中…(img-EcPkbTgu-1714760542713)]

除此之外,前文所提及的Alibaba珍藏版mybatis手写文档以及一本小小的MyBatis源码分析文档——《MyBatis源码分析》等等相关的学习笔记文档,也皆可分享给认可的朋友!

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值