一、简介
Ribbon是Netflix发布的负载均衡器,它有助于控制HTTP和TCP的客户端的行为。
Ribbon是Spring Cloud核心组件之一,它提供的最重要的功能就是负载均衡
,和硬件负载均衡F5
不同,它的负载均衡是基于客户端
的,Zuul网关和Feign可以通过Ribbon轻松的实现服务的负载均衡,同时避免了与业务无关的冗余代码。
Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括后续我们将要介绍的Feign,它也是基于Ribbon实现的工具。
二、如何利用Ribbon实现负载均衡
(1)在spring boot的启动类中注入RestTemplate的地方加上@LoadBalanced即可实现负载均衡。
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
@LoadBalanced注解是属于Spring,而不是Ribbon的,Spring在初始化容器的时候,如果检测到Bean被@LoadBalanced注解,Spring会为其设置LoadBalancerInterceptor的拦截器
(2)@LoadBalanced原理
RestTemplate在发送请求的时候会被ClientHttpRequestInterceptor拦截,LoadBalancerInterceptor是ClientHttpRequestInterceptor的实现类,它的作用就是用于RestTemplate的负载均衡,LoadBalancerInterceptor将负载均衡的核心逻辑交给了loadBalancer。
1)拦截:
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
2)处理:
RibbonLoadBalancerClient-DynamicServerListBalancer
getLoadBalancer()-拉取服务列表
getServer()-负载均衡
(3)@LoadBalanced注解定义
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
这个注解可以注释在类变量(field),方法参数(parameter)上,这个注解也被@Qualifier修饰,目的就是为了Spring容器注入参数的时候,只选择注入被LoadBalanced注解修饰的bean对象。
(4)负载均衡流程
下图引用自:https://zhuanlan.zhihu.com/p/262660637
三、负载均衡策略
Ribbon的负载均衡策略类都是实现的IRule接口,如下图所示:
(1)AbstractLoadBalancerRule
负载均衡策略的抽象类,在该抽象类中定义了负载均衡器ILoadBalancer对象,该对象能够在具体实现选择服务策略时,获取到一些负载均衡器中维护的信息来作为分配依据,并以此设计一些算法来实现针对特定场景的高效策略。
(2)RandomRule
该策略实现了从服务实例清单中随机
选择一个服务实例的功能。具体的选择逻辑在一个while(server==null)循环之内,而根据选择逻辑的实现,正常情况下每次选择都应该选出一个服务实例,如果出现死循环获取不到服务实例,如果出现死循环获取不到服务实例时,则很有可能存在并发的Bug。
(3)RoundRobinRule
该策略实现了按照线性轮询
的方式的方式一次选择每个服务实例的功能。它的具体实现如下,其详细结构与RandomRule非常类似。除了循环条件不同外,就是从可用列表中获取所谓的逻辑不通。从循环条件中,我们可以看到增加了一个count计数变量,该变量会在每次循环之后累加,也就是说,如果一直选择不到server超过10次,那么就会结束尝试,并打印一个警告信息No available alive servers after 10tries from load balancer : … 。
(4)RetryRule
该策略实现了一个具备重试机制
的实例选择功能。从下面的实现中我们可以看到,在其内部还定义了一个IRule对象,默认使用了RoudRobinRule实例。而在choose方法中则实现了对内部定义的策略进行反复尝试的策略,若期间能够选择到具体的服务实例就反悔,若选择不到就根据设置结束时间为阀值(maxRetryMillis参数定义的值+choose方法开始执行的时间戳),当超过该阀值就返回null。
(5)WeightedResponseTimeRule
该策略是对RoundRobinRule的扩展,增加了根据实例等运行情况来计算权重,并根据权重来挑选实例,以达到更优的分配效果,它的实现主要有三个核心内容。
定时任务、 权重计算、实例选择
(6)ClientConfigEnabledRoundRobinRule
该策略较为特殊,我们一般不直接使用它,因为它本身并没有实现什么特殊的处理逻辑,在它的内部定义了一个RoundRobinRule策略,而choose函数的实现也正是使用了RoundRobinRule的线性轮询机制,所以它实现的功能实际上与RoundRobinRule相同,那么定义它有什么特殊的用处呢?
虽然我们不会直接使用该策略,但是通过继承该策略,默认的choose就实现了线性轮询机制,在子类中做一些高级策略时通常有可能会存在一些无法实施的情况,那么久可以用父类的实现作为备选。在后面中我们将继续介绍的高级策略均是基于ClientConfigEnabledRoundRobinRule的扩展。
(7)BestAvailableRule
该策略继承自ClientConfigEnabledRoundRobinRule,在实现中它注入了负载均衡器的统计对象LoadBalancerStats,同时在具体的choose算法中利用LoadBalancerStats保存的实例统计信息来选择满足要求的实例。从如下源码中我们可以看到,它通过遍历负载均衡器中维护的所有服务实例,会过滤掉故障的实例,并找出并发请求数最小的一个,所以该策略的特性是可选出最空闲的实例。
同时,由于该算法的核心依据是统计对象LoadBalancerStats,当其为空的时候,该策略是无法执行的。所以从源码中我们可以看到,当loadBalancerStats为空的时候,它会采用父类的线性轮询策略,正如我们在介绍ClientConfigEnabledRoundRobinRule时那样,它的子类在无法满足实现高级策略的时候,可以使用线性轮询策略的特性。后面将要介绍的策略因为也都继承自ClientConfigEnabledRoundRobinRule,所以它们都会具有这样的特性。
(8)PredicateBasedRule
这是一个抽象策略,它也继承了ClientConfigEnabledRoundRobinRule,从其命名中可以猜出这是一个基于Predicate实现的策略
,Predicate是Google Guava Collections工具对集合进行过滤掉条件接口。
Google Guava Collections是一个对Java Collections Framework增强和扩展的开源项目。虽然Java Collections Framework已经能够满足我们大多数情况下使用集合的要求,但是当遇到一些特殊情况时,我们的代码会比较冗长且容易出错。Google Guava Collections可以帮助我们让集合操作代码更为简短精炼并大大增强代码的可读性
(9)AvailabilityFilteringRule
该策略继承自上面介绍的抽象策略PredicateBasedRule,所以它也继承了“先过滤清单,再轮询选择”的的基本处理逻辑,其中过滤条件使用了AvailabilityPredicate。简单地说,该策略通过线性抽样的方式直接尝试寻找可用且较空闲的实例来使用,优化了父类每次都要遍历所有实例的开销。
(10)ZoneAvoidanceRule
该策略我们在介绍负载均衡器ZoneAwareLoadBalancer时已经提到过,它也是PredicateBasedRule的具体实现类,在之前的介绍中主要针对ZoneAvoidanceRule中用于选择Zone区域策略的一些静态函数,比如createSnapshot(LoadBalancerStats lbStats)、getAvailableZones(Map snapshot, double triggeringLoad,double triggeringBlackoutPercentage)。在这里我们将详细看看ZoneAvoidanceRule作为服务实例过滤条件的实现原理。从下面ZoneAvoidanceRule的源码片段中可以看到,它使用了CompositePredicate来进行服务实例清单的过滤。这是一个组合过来条件,在其构造函数中,它以ZoneAvoidanceRule为主过滤条件,AvailabilityPredicate为次过滤条件初始化了组合过滤条件的实例。
四、负载均衡策略调整
1、代码方式
在消费者服务的启动类中定义一个新的IRule:
@Bean
public IRule randomRule(){
return new RandomRule();
}
作用域:全局
,调用任何服务都是随机
2、配置文件方式
在消费者服务的配置文件application.yml中添加新得配置:
privoiderservice:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
作用域:局部
,针对某个微服务而言
五、Ribbon的饥饿加载
1、懒加载(默认):
第一次访问时才创建LoadBalanceClient
2、饥饿加载:
服务启动时就创建好LoadBalanceClient,这样请求来了就可以直接用
3、配置饥饿加载:
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: # 指定对某个服务进行饥饿加载
-service1
-service2
参考文章:
https://www.jianshu.com/p/1bd66db5dc46
https://zhuanlan.zhihu.com/p/262660637