【微服务七】Ribbon负载均衡策略之BestAvailableRule源码深度剖析

网友投稿 325 2022-08-23

【微服务七】Ribbon负载均衡策略之BestAvailableRule源码深度剖析

文章目录

​​一、前言​​​​二、BestAvailableRule​​

​​1、负载规则​​​​2、如何判断服务实例被tripped?​​​​3、如何获取服务实例的并发数?​​​​4、实例并发数的维护:​​

​​1)增加实例的并发数​​​​2)减少实例的并发数​​

​​三、后续文章​​

一、前言

前置Ribbon相关文章:

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

我们聊了以下问题:

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

本文继续讨论 最佳可用规则(​​BestAvailableRule​​)是如何实现的?

PS:Ribbon依赖Spring Cloud版本信息如下:

org.springframework.boot spring-boot-dependencies 2.3.7.RELEASE pom import org.springframework.cloud spring-cloud-dependencies Hoxton.SR8 pom import com.alibaba.cloud spring-cloud-alibaba-dependencies 2.2.5.RELEASE pom import

二、BestAvailableRule

BestAvailableRule会逐个考察Server,如果Server被tripped了,则跳过;最终选择一个并发请求量最小的Server。

1、负载规则

我们知道Ribbon负载均衡算法体现在IRule的choose(Object key)方法中,所以看BestAvailableRule的choose(Object key)方法:

详细代码注释如下:

public class BestAvailableRule extends ClientConfigEnabledRoundRobinRule { // 维护了服务实例的一些状态信息 private LoadBalancerStats loadBalancerStats; @Override public Server choose(Object key) { // 如果服务实例状态信息为空,则直接使用父类的choose()方法,采用RoundRobin算法 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); // 如果服务实例被tripped了,则直接跳过当前服务实例 if (!serverStats.isCircuitBreakerTripped(currentTime)) { // 获取实例的并发数(当且仅当 当前时间与上次有效更改连接数的时间间隔在指定范围之内(默认10分钟)) int concurrentConnections = serverStats.getActiveRequestsCount(currentTime); // 找到并发连接最小的那个服务实例 if (concurrentConnections < minimalConcurrentConnections) { minimalConcurrentConnections = concurrentConnections; chosen = server; } } } // 如果遍历完所有的服务实例之后,还没有找到server,则调用父类的choose()方法,用RoundRobin算法进行选择。 if (chosen == null) { return super.choose(key); } else { return chosen; } } @Override public void setLoadBalancer(ILoadBalancer lb) { super.setLoadBalancer(lb); if (lb instanceof AbstractLoadBalancer) { loadBalancerStats = ((AbstractLoadBalancer) lb).getLoadBalancerStats(); } }}

方法的核心逻辑:

首先判断如果服务实例状态信息为空,则直接使用父类的choose()方法,采用RoundRobin算法。否则:从BestAvailableRule所属的ILoadBalancer中获取服务的所有实例,记录当前时间;遍历服务的每个实例,获取实例的​​ServerStats​​​,如果实例被tripped了,则直接跳过当前服务实例;否则,获取实例的并发数(这里当且仅当 当前时间与上次有效更改连接数的时间间隔在指定范围之内(默认10分钟)),如果​​超过了时间范围则返回0​​。循环结束后返回并发数最小的第一个实例。最后,如果遍历完所有的服务实例之后,还没有得到Server,则调用其父类的choose()方法,使用RoundRobin算法选择一个实例。

下面我们接着看几个细节点:如何判断服务实例被tripped?如何获取服务实例的并发数?

2、如何判断服务实例被tripped?

逻辑体现在​​ServerStats​​​的​​isCircuitBreakerTripped(long currentTime)​​方法中:

public boolean isCircuitBreakerTripped(long currentTime) { // 获取断路器超时时间点 long circuitBreakerTimeout = getCircuitBreakerTimeout(); // 如果断路器超时时间点 <= 0,则直接返回false。 if (circuitBreakerTimeout <= 0) { return false; } // 如果断路器超时时间点 > 当前时间,则返回true,表示服务实例被tripped了;否则返回false return circuitBreakerTimeout > currentTime;}

方法核心逻辑:

判断断路器超时时间点是否大于当前时间,如果大于,则表示当前服务实例被tripped了,也就不会再被选择;否者,正常选择。

3、如何获取服务实例的并发数?

逻辑体现在​​ServerStats​​​的​​getActiveRequestsCount(long currentTime)​​方法中:

public int getActiveRequestsCount(long currentTime) { // 获取实例当前的并发连接数 int count = activeRequestsCount.get(); // 连接数为0,则直接返回0 if (count == 0) { return 0; // 如果当前时间与上次有效更改连接数的时间间隔不在指定范围之内(默认10分钟),则并发连接数设置为0,并返回0 } else if (currentTime - lastActiveRequestsCountChangeTimestamp > activeRequestsCountTimeout.get() * 1000 || count < 0) { activeRequestsCount.set(0); return 0; } else { // 正常场景下返回并发连接数 return count; }}AtomicInteger activeRequestsCount = new AtomicInteger(0);private static final DynamicIntProperty activeRequestsCountTimeout = DynamicPropertyFactory.getInstance().getIntProperty("niws.loadbalancer.serverStats.activeRequestsCount.effectiveWindowSeconds", 60 * 10);

关键点在于实例的并发数是如何维护的?下面我就接着看。

4、实例并发数的维护:

1)增加实例的并发数

在开始执行一个Rest请求时会通过ServerStats#​​incrementActiveRequestsCount()​​​方法新增一个连接数(​​activeRequestsCount​​);

虽然是在new一个​​RibbonStatsRecorder​​​时新增的实例并发数,但是RibbonStatsRecorder内部组合的ServerStats来源于Ribbo的上下文​​RibbonLoadBalancerContext​​​,所以每次new ​​RibbonStatsRecorder​​时,ServerStats数据是共享的;

2)减少实例的并发数

当Rest请求调用外部服务执行完毕之后,会通过ServerStats#​​decrementActiveRequestsCount()​​​方法减少一个连接数(​​activeRequestsCount​​):

RibbonStatsRecorder#recordStats(Object entity) 方法如下:

三、后续文章

下一篇文章我们接着分析Ribbon负载均衡策略之WeightedResponseTimeRule。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:OpenHarmony如何选择图片在Image组件上显示(eTS)
下一篇:【云原生&微服务五】Ribbon负载均衡策略之随机ThreadLocalRandom
相关文章

 发表评论

暂时没有评论,来抢沙发吧~