【Spring Cloud】Ribbon负载均衡原理与实战(源码级讲解)




1. 负载均衡原理


1.1 总体流程

  • 上一章的案例中,订单微服务 order-service 发起的远程调用 URL http://userservice/user/1 并不是一个真实的 URL 。在浏览器中无法访问该 URL 。
  • 这是因为,这个 HTTP 请求被 Ribbon 拦截,由 Ribbon 解析 URL 中的 /userservice ,根据注册服务名称前往 Eureka 的注册列表中查询这个服务名称下的所有服务实例的真实 IP 地址和端口。然后 Ribbon 根据 Eureka 返回的服务实例名单,采用某种负载均衡策略从中选取一个服务实例进行调用。

image-20221123153852592


1.2 源码解析

  • 订单微服务 OrderApplication.java 发起的 HTTP 远程调用请求,首先被 Spring Cloud 的 commons 包下的 LoadBalancerInterceptor.java 负载均衡拦截器拦截下来。

    image-20221215174721935

    LoadBalancerInterceptor.java 实现了 Spring Web 包下的ClientHttpRequestInterceptor.java 客户端 HTTP 请求拦截器接口,它拦截所有客户端的 HTTP 请求。而 RestTemplate.java 正是发起客户端 HTTP 请求,因此会被这个拦截器拦截。

    image-20221215175629828

    ClientHttpRequestInterceptor.java 接口定义了一个方法 intercept()

    @FunctionalInterface
    public interface ClientHttpRequestInterceptor {
          
          
    
       ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
             throws IOException;
    
    }
    
  • 再回来查看这个接口的实现类 LoadBalancerInterceptor.java 是如何实现 intercept() 方法的。

  • LoadBalancerInterceptor.java

    @Override
    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,
             this.requestFactory.createRequest(request, body, execution));
    }
    

    在这个方法上打断点进行逐行 Debug 。在 Postman 中发起一个查询订单的请求 http://localhost:8080/order/101

    扫描二维码关注公众号,回复: 15004245 查看本文章
  • 可以看到 LoadBalancerInterceptor.java 的职责是拦截 RestTemplate 发起的 HTTP 请求;然后取出这个 HTTP 请求的 URL 中的微服务名称 userservice ;最后,把截取到的微服务名称 userservice 输出给 this.loadBalancerexecute() 方法来处理。

    image-20221216092544945

    鼠标悬停在 this.loadBalancer 上可以看到,它是 RibbonLoadBalancerClient.java 的对象,到这里,我们终于见到 Ribbon 负载均衡器本体了。我们点进 this.loadBalancerexecute() 方法中去看。

  • 点击后就进入了 RibbonLoadBalancerClient.javaexecute() 方法。再点进,就进入了真正的 RibbonLoadBalancerClient.javaexecute() 方法。

    image-20221216094205716

    在这里,微服务名称被称为 serviceId ,输入到 getLoadBalancer() 方法中,此方法根据微服务名称向 Eureka 拉取服务列表,返回一个名为 DynamicServerListLoadBalancer 的动态服务器列表负载均衡器。

    image-20221216094457942

    点开查看其属性 allServerList ,两个用户微服务 UserApplication 的 IP 地址和端口赫然在列。说明此时 Ribbon 已经成功向 Eureka 拿到用户微服务的服务实例列表了。

    image-20221216094833675

    接下来,就应该在拉取到的服务列表中,采用负载均衡策略选取出一个服务实例。这就是下一行代码中 getServer() 方法做的事。我们点进这个方法查看。

    image-20221216095710409

    点进 getServer() 方法中,可以看到其调用了 chooseServer() 方法,顾名思义该方法用于选取服务实例。继续点进这个方法。

    image-20221216100129037

  • 就进入了区域感知负载均衡器 ZoneAwareLoadBalancer.javachooseServer() 方法。发现它调用了父类 BaseLoadBalancer.javachooseServer() 方法。继续点进这个方法。

    image-20221216101309561

  • 点进来 BaseLoadBalancer.java 后往下走,我们就看到了调用 rule.choose() 方法。rule 是负载均衡策略,点击 rule 属性查看其是什么类的对象。

    image-20221216102313883

    可以看到,rule 是策略接口 IRule.java 的对象。默认的负载均衡策略是 ZoneAvoidanceRule (区域回避策略,在下一节详细讲解) 。

    image-20221216102832159

    按 Ctrl + H 查看 IRule.java 接口的所有实现类。其中有我们熟悉的 RoundRobinRule 轮询策略和 RandomRule 随机策略,这都是之前在 Nginx 负载均衡策略中学过的。

    image-20221216103206706

  • 看到这里,不再深挖了,点顺序执行往回走,走回 RibbonLoadBalancerClient.javaexecute() 方法中。发现 getServer() 方法使用 ZoneAvoidanceRule 负载均衡策略选取到了端口为 8082 的用户微服务 UserApplication ,接下来就可以把之前的远程调用 URL 中的服务名称 /userservice 替换成 8082 的 IP 地址和端口号 /localhost:8082 了,去发起真实的 HTTP 远程调用请求 http://localhost:8082/user/userId

    image-20221216110119474

  • Ribbon 负载均衡源码就解析到这里。总结全流程如下图所示:

    image-20221123155421535


2. 负载均衡策略


2.1 负载均衡策略继承关系

  • Ribbon 的负载均衡策略是一个叫 IRule.java 的接口来定义的,其每个子接口都是一种策略。

image-20221123160206803

  • 上一节 Ribbon 源码中,我们聊到的默认策略是 ZoneAvoidanceRule ,就在上图的最底层右侧。初次接触这个词可能很陌生,我们不妨往上看它的爷爷 ClientConfigEnableRoundRobinRule ,顾名思义,从后面几个单词 RoundRobin 中我们也不难看出,这是轮询策略的一种,只是在轮询的基础上作了加强。那么作为孙子的 ZoneAvoidanceRule ,我们就可以大胆推测,它也是轮询策略的一种。具体看下表。

2.2 负载均衡策略描述

  • 下表中罗列了 Ribbon 中常见的负载均衡策略。

image-20221123160421634


1)ZoneAvoidanceRule

  • 其中第 4 行,就是默认策略 ZoneAvoidanceRule 。我们在配置服务注册时,是可以设置微服务的 Zone 值 ( Zone 值可以理解为一个地区代码,比如服务器机房在北京、上海、深圳等) 。设置 Zone 值后,Ribbon 作轮询时,会优先选择跟自己在同一个 Zone 内的微服务 (即就近原则) ,然后再在多个微服务实例中作轮询。
  • 因此,这个 Zone 值往往只有大厂才会设置,因为大厂需要在多地搭建机房提升容灾能力和并发能力,中小公司往往所有的服务器都在一个机房,因此没有必要配置 Zone 而是直接轮询。

2)AvailabilityFilteringRule

  • 表中第 2 行,中文名是可用性筛选策略。会排除网络环境差和并发数高的服务,在剩下的服务中去做轮询。

  • 其他策略都很好理解,一般情况下选择默认的 ZoneAvoidanceRule 即可。


2.3 修改负载均衡策略方式


1)全局修改

  • 消费者服务远程调用任何其他提供者微服务都起作用。例如,订单微服务 order-service 远程调用用户微服务 user-service 时起作用,远程调用购物车微服务时也起作用。

  • 全局修改步骤:在订单微服务 order-service 中的启动类 OrderApplication.java 定义一个新的 IRule 。

  • OrderApplication.java

    // 全局修改负载均衡策略为随机策略
    @Bean
    public IRule randomRule() {
          
          
        return new RandomRule();
    }
    

2)局部修改

  • 消费者远程调用时,只在调用某个提供者微服务时起作用。例如,局部修改负载均衡策略成 RandomRule 时,订单微服务 order-service 远程调用用户微服务 user-service 时起作用,远程调用任何其他提供者微服务不起作用 (还是默认策略) 。

  • 局部修改负载均衡策略步骤:在订单微服务 order-service 中的配置文件 application.yml 中添加 IRule 负载均衡策略。

  • application.yml

    userservice:
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule  # 负载均衡策略
    

3. 饥饿加载


3.1 背景

  • 懒加载,又称懒汉式加载,思想与单例设计模式中的懒汉式相同。Ribbon 默认采用懒加载,即第一次访问到来时才会去创建 LoadBalanceClient ,好处是启动时间快,坏处是用户第一次访问的请求时间耗时长。
  • 饥饿加载,又称饿汉式加载 ,思想与单例设计模式中的饿汉式相同。会在项目启动时就创建,好处是降低用户第一次访问的请求耗时,坏处是服务启动时间变长。
  • 在实际企业开发中,企业是宁愿服务启动耗时长一些,也要保障用户体验的。因此我们把 Ribbon 的加载方式修改成饥饿加载。

3.2 启用饥饿加载

  • 打开订单微服务 order-service 中的配置文件 application.yml

  • application.yml

    ribbon:
      eager-load:
        enabled: true # 开启饥饿加载
        clients:
          - userservice  # 指定只对用户微服务userservice启用饥饿加载
          - xxservice	# 指定多个
    

3.3 对比测试

  • 采用懒加载,重启订单微服务 order-service 后,发送第一次请求 http://localhost:8080/order/101 。耗时要 1639 ms ,这就非常离谱了,用户能明显感知到卡顿延迟。

    image-20221216150715626

  • 修改成饿汉式加载后,重启订单微服务 order-service 后,服务一启动就作服务发现。虽然服务启动需要更长时间,但能显著缩短用户第一次访问的耗时。修改后访问耗时只需要 1639 ms , (从 550 ms 提升到 20 ms) 。

    image-20221216151018112

猜你喜欢

转载自blog.csdn.net/Sihang_Xie/article/details/128343362