# 负载均衡器:Ribbon
在 Spring Cloud 体系中,发起远程调用,本质上就是发起 HTTP 请求。
服务的提供者本质上就是一个 RESTful 风格的 Web 服务,因此,在知道其 API 的情况下我们只要能够发出 HTTP 请求,实际上就『调用』到了这个服务。
# 1. Ribbon 在 RestTemplate 中的负载均衡
在你没有意识到 Ribbon(读作 [ˈrɪbən] )存在的时候,Ribbon 就已经可以在你的项目中(配合 RestTemplate)起作用了。
为你的 RestTemplate 的 @Bean 加上 @LoadBalanced 注解:
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
@LoadBalanced 注解背后就是 Spring AOP 动态代理的思想。
你循环调用 RestTemplate,以目标服务在 Nacos Server 上注册的名字来代替 URL 中的 IP 地址,你就会发现有负载均衡的效果:
String url = "http://xxx-service/hello";
for (int i = 0; i < 10; i++) {
String str = template.postForObject(url, null, String.class);
System.out.println(str);
}
这里之所以能出现负载均衡现象是因为:
你的项目间接引用到了 Ribbon ,spring-cloud-starter-alibaba-nacos-discovery 包中已经包含了 ribbon;
当然,你也可以单独引用(spring-cloud-starter-netflix-eureka-ribbon),不过,逻辑上这就是一句啰嗦的废话。
你的项目是一个 Nacos Client 项目,当它启动时,它会去 Nacos Server 上拉取已注册的所有的服务的 IP 地址等相关信息;
当你通过 RestTemplate 以『application name』为依据发出请求时,Ribbon 会参与进来,会将 application-name『替换』成上述第 2 步中查到的 IP 地址。
在『替换』的过程中,它以某种规则轮流使用同一个服务的多个实例的 IP 地址,从而实现负载均衡效果。这里的『某种规则』指的就是负载策略。
默认的负载策略是轮循。
注意
使用了 @LoadBalanced 注解之后,RestTemplate 的 URL 中不能再出现 IP:Port,哪怕只有一个单点,也只能出现目标服务在 Nacos Server 上注册的 name ,反之亦然。
Spring Cloud Ribbon 不像注册中心、配置中心、网关那样独立部署、运行。它更像是一个工具类(库),『嵌套』在各个组件中配合其它组件使用。
# 2. Ribbon 的执行原理
Ribbon 是以拦截器的方式『参与』到 RestTemplate 的请求发送功能中的。
当我们 RestTemplate 执行请求操作时,就会被 Ribbon 的拦截器拦截。Ribbon 通过 request.getURI()
方法求能获得 RestTemplate 所发出的请求的 URI,不过此时 URI 中的内容是目标服务的 application-name 而非 IP 。
由于我们的项目作为 Nacos Client 能够从 Nacos Server 上拉取到 application-name 所对应的一个(或多个)IP 地址及端口信息,因此,Ribbon 会根据某种『规则』(算法)获取到目标服务的真实的访问路径。
最后,Ribbon 放行 RestTemplate,让它向目标服务继续发起请求(并获取返回)。
# 3. 负载均衡策略和 IRule 接口
前面提到过的『规则』、『算法』就是『负载均衡策略』。
Ribbon 内置了 8 种负载均衡策略(其实是 7 种),它们都直接或间接实现了 IRule 接口:
其中常见的有:
策略 | 说明 |
---|---|
RandomRule | 随机策略。随机选择目标服务的实例。 |
RoundRobinRule 默认 | 轮询策略。按顺序循环选择目标服务的实例。 |
WeightedResponseTimeRule | 根据响应时间分配一个 Weight(权重),响应时间越长,Weight 越小,被选中的可能性越低。 这个策略以前版本中被称作 ResponseTimeWeightedRule 。 |
BestAvailablRule | 这种策略下,Ribbon 会观测、统计目标服务的各个实例的并发量。 当再次发起对目标服务的访问时,Ribbon 会先过滤掉因为多次访问故障而被标记为 Error 的 实例。然后选择一个并发量(ActiveRequestCount)最小的实例发起访问。 俗话说就是:先去掉不能干活的,然后在能干活的里面找一个最闲的。 |
说明
上述 4 种策略简单高效,使用较多。而 AvailabilityFilteringRule 和 ZoneAvoidanceRule 策略需要结合断路、超时等参数配置,使用起来比较复杂,容易进坑,所以使用较少。
Ribbon 默认的负载均衡策略是:轮询 。如果我们想调整一下负载均衡策略,可以通过如下的配置。在『服务消费者』的服务中(即使用 RestTemplate 发起请求的一方),做 Ribbon 负载均衡策略的调整。
目前最简单的方式就是:
注意
以上配置的仅针对 application-name 为 xxx-service 的微服务有效。即,若你要调用多个不同的微服务,你要对它们一一配置。
# 4. Ribbon 的饥饿加载
默认情况下,服务消费方调用服务提供方接口的时候,第一次请求会慢一些,甚至会超时,而之后的调用就没有问题了。
这是因为 Ribbon 进行客户端负载均衡的 Client 并不是在服务启动的时候就初始化好的,而是在调用的时候才会去创建相应的 Client,所以第一次调用的耗时不仅仅包含发送HTTP请求的时间,还包含了创建 RibbonClient 的时间,这样一来如果创建时间速度较慢,同时设置的超时时间又比较短的话,很容易就会出现上面所描述的现象。
你可以通过启用 Ribbon 的饥饿加载(即,立即加载)模式,并指定在项目启动时就要加载的服务:
# 5. Ribbon 的超时和超时重试
Ribbon 是有超时设置,以及超时之后的重试功能的。早年,Ribbon 的超时设置和重试设置的配置方式一直在变动,因此有很多『配置无效』的现象,十分诡异。
Ribbon 的超时重试总次数是
(1 + MaxAutoRetries ) x (1 + MaxAutoRetriesNextServer)
# 6. Ribbon 整合 RestTemplate 底层原理概述
RestTemplate 有拦截器机制,类似于 Spring MVC 的拦截器机制,在 RestTemplate 去做 “真正的工作” 前,会先执行其所有的拦截器,然后才开始 “干正事” 。
很显然,默认情况下 RestTemplate 没有任何拦截器,而在和 Ribbon 整合后,Ribbon 为 RestTemplate 添加了一个拦截器,其作用就是向 Ribbon 组件 “要” 一个真实可用的 IP 和端口( 根据服务名 “要” ),这样一来,在随后 “干正事” 时,RestTemplate 就可以向目标发起 HTTP 请求,并获取响应。
在 Ribbon 为 RestTemlate 添加的拦截器的工作过程中,Riibon 的负载均衡配置就在这个环节起作用,根据各种配置策略,返回不同实例的服务地址。