# 三、Ribbon 负载均衡🎄

  • 负载均衡原理
  • 负载均衡策略
  • 懒加载

# 3.1、负载均衡流程🌳

直接通过 http:userservice/user/1 无法访问。并 <font color='red'> 不是 </font > 一个 <font color='red'> 真实可用的地址 </font>.

当 order-service 发起请求时,是无法到达 user-service 服务的,因此一定会有人将请求拦截下来做一下处理找到真是 ip 和端口才行

image-20231005085431639

order-service 发送请求被 Ribbon 拦截它得到请求指定的是哪个服务 (userservice),然后就去找 eureka 拉取 userservice 的服务列表。

拿到服务列表后再进行负载均衡挑选一个进行请求

image-20231004194527737

具体 Ribbon 什么时候拦截处理这些请求的具体看源码:

在 RestTemplate 的 Bean 中添加的 @LoadBalanced 注解就代表将来 RestTemplate 发起的请求要被 Ribbon 进行拦截和处理了。

@Bean
@LoadBalanced
public RestTemplate restTemplate()
{
   return new RestTemplate();
}

那么拦截的动作是谁来完成的呢?

是 LoadBalancerInterceptor 来完成的。它实现了一个接口 ClientHttpRequestInterceptor 这个接口是干什么呢的?

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

ClientHttpRequestInterceptor 它会去拦截由客户端发起的 http 请求,而 RestTemplate 不正是一个发 http 请求的客户端吗,所以就会被这个拦截器所拦截

ClientHttpRequestInterceptor 这个接口中定义的一个方法为:intercept

ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException;

那么我们回到它的实现类 LoadBalancerInterceptor 这个拦截器既然实现了 ClientHttpRequestInterceptor 接口就一定会实现 intercept 方法

我们在重写方法处打上断点 * 表示断点

public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
*   URI originalUri = request.getURI();
   String serviceName = originalUri.getHost();
   Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
   return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}

我们可以认为由 RestTemplate 发起的请求就一定会被 intercept 拦截

开启 debug 访问 url:http://127.0.0.1:8080/order/101

就会执行到断点处

image-20231005093021344

证明这个请求确实被拦截了,获取请求地址拿到的是 http://userservice/user/1 这不就是哪个无法直接访问的地址吗

image-20231005093231384

public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
   // 获取请求地址
   URI originalUri = request.getURI(); // http://userservice/user/1
   // 获取主机名 (不是指本地主机名) url 中的主机名也就是服务名称
   // 拿到服务名称后找 eureka 拉取服务列表信息
   String serviceName = originalUri.getHost(); // serviceName: userservice
   // 得到服务名称后怎么完成拉取的呢
   Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
   // Ribbon 的负载均衡客户端去执行
   return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}

进入 execute 方法,执行 getLoadBalancer 得到一个对象,这个对象名为:DynamicServerListLoadBalancer 就是动态服务列表负载均衡器

image-20231005094542640

在服务列表中可以看到 8081,8082 成功的被拉取到了已经。

image-20231005094741094

所以 getLoadBalancer 这个方法就是在根据服务名称找 eureka 拉取服务列表,下一步就是负载均衡了

进入 getServer 看看是怎么做的

image-20231005094951601

rule (规则) 在 DynamicServerListLoadBalancer 中选择一个那么选择时是有一种规则来选的那么这个 rule 是个什么类型对象呢

image-20231005095131490

叫做 IRule (I 就是接口的意思 Rule 就是规则的意思) 那么就是一个规则的接口的意思,那它一定会有实现类通过 ctrl+h 查看实现类

image-20231005095318356

它的实现类都有以下几个

RandomRule:随机规则

RoundRobinRule:轮询调度

image-20231005095544615

而它默认的负载均衡规则:ZoneAvoidanceRule

那么看到这里我们就清楚了负载均衡的策略是由 IRule 接口来决定的

然后进行返回 getServer 拿到一个服务为 8081,拿到服务信息了就可以用真是的 ip,端口来替代原来的 http://userservice/user/1 服务名称。去发送真实的请求了。

image-20231005095838892

总结一下

当请求进入 Ribbon 后会怎么去处理呢,请求会被一个拦截器 拦截名为:LoadBalancerInterceptor 拦截后会得到请求的服务名称 userservice 然后将服务名称交给 RibbonLoadBalancerClient 它再将服务交给 DynamicServerListLoadBalancer 它会去 eureka 中拉取服务列表信息,然后从服务列表中挑选一个而这个负载均衡是 IRule 来做的根据规则挑一个出来选中了 8081 服务,将值返回给 RibbonLoadBalancerClient 然后用这个 ip,端口替换服务名称得到真实的请求地址最后就请求到了 8081 服务

这就是整个 Ribbon 工作的流程了!

image-20231004203247209

# 3.1.1、IRule 接口实现🌲

IRule 接口决定了负载均衡的策略,下面详细学习 IRule 这个接口都有哪些实现以及自己如何修改它的每个实现

Ribbon 的负载均衡规则是一个叫做 IRule 的接口来定义的,每一个子接口都是一种规则:

<center>IRule 接口继承关系结构图 </center>

image-20231004203629391

# 3.2、负载均衡策略🌳

内置负载均衡规则类规则描述
RoundRobinRule简单轮询服务列表来选择服务器。它是 Ribbon 默认的负载均衡规则
AvailabilityFilteringRule对以下两种服务器进行忽略:<br />(1) 在默认情况下,这台服务器如果 3 次连接失败,这台服务器就会被设置为 “短路” 状态。短路状态将 < br /> 持续 30 秒,如果再次连接失败,短路的持续就会几何级的增加。<br />(2) 并发数过高的服务器。如果一个服务器的并发连接数过高,配置了 AvailabilityFilterngRule 规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的 < clientName></clientConfigNameSpace>.ActiveConnectionsLimit 属性进行配置。
WeightedResponseTimeRule为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。
<font color='red'>ZoneAvoidanceRule</font>以区域可用的服务器为基础进行服务器的选择。使用 Zone 对服务器进行分类,这个 Zone 可以理解为一个机房,一个机架等。而后再对 Zone 内的多个服务做轮询。
BestAvailableRule忽略哪些短路的服务器,并选择并发数较低的服务器。
RandomRule随机选择一个可用的服务器。
RetryRule重试机制的选择逻辑。

通过定义 IRule 实现可以修改负载均衡规则,有两种方式:

1、代码方式:在 order-service 中的 OrderAppliction 类中,定义一个新的 IRule

这样就会让负载均衡规则从轮询变成随机规则

@Bean
public IRule randomRule()
{
   return new RandomRule();
}

2、配置文件方式:在 order-service 的 application.yml 文件中,添加新的配置也可以修改规则:

先指定服务名称,再去执行负载均衡规则因此这种配置方案它是针对某个微服务而言的。

userservice: # 服务名称
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则

# 3.3、饥饿加载🌳

Ribbon 默认是采用懒加载,即第一次访问时才会去创建 LoadBalanceClient,请求时间会很长。

而饥饿加载则会在项目启动时创建,降低第一次访问时的耗时,通过下面配置开启饥饿加载:

userservice: # 服务名
  ribbon:
    # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
    eager-load:
      enabled: true # 开启饥饿加载
      clients: userservice # 指定对 userservice 这个服务饥饿加载
      # 如果多个服务需要饥饿加载则写如下格式
      # clients:
      #     - userservice
      #     - 服务名 1
      #     - 服务名 2

总结

  1. Ribbon 负载均衡规则
    • 规则接口是 IRule
    • 默认实现是 ZoneAvoidanceRule,根据 zone 选择服务列表,然后轮询
  2. 负载均衡自定义方式
    • 代码方式:配置灵活,但修改时需要重新打包发布
    • 配置方式:直观,方便,无需重新打包发布,但是无法做全局配置
  3. 饥饿加载
    • 开启饥饿加载
    • 指定饥饿加载的微服务名称