关于ribbon:分布式springcould服务调用Ribbon的负载均衡

3次阅读

共计 6663 个字符,预计需要花费 17 分钟才能阅读完成。

[TOC]

之前咱们介绍了治理分布式组件注册的服务;eureka、consul、zookeeper、nacos 他们都能够实现咱们服务的注册于获取。
然而理论咱们还是须要咱们本人调用最终的客户端获取数据的。

前提概要

  • 下面的服务发现框架都能够应用。consul 因为须要保障网络通信失常。而 eureka 是咱们本人注册 java 服务。所以这里就抉择通过 eureka 来作为咱们的服务治理框架。
  • 这里咱们就借助咱们之前 eureka 服务搭建整合文章。咱们间接用之前那个分支启动 eureka 服务,一个 order 服务、两个 payment 服务。

  • 而后还是拜访咱们 localhost/order/payment/123 这个接看看响应是否是负载平衡的。

  • 这些都是咱们 eureka 章节的内容。这个时候问题来了。为什么 restTemplate 会实现负载平衡。这里咱们查阅材料就会发现在服务治理框架中会注入 ribbon 框架。在 ribbon 注册的时候回将 ribbon 提供的拦截器注入到 restTemplate 中。restTemplate 执行之前会先走拦截器从而实现负载平衡。
  • 所以重点还是在 ribbon。因为是他实现了负载平衡

Ribbon 作用

  • ribbon 是 springcloud 我的项目组件。全名 spring-cloud-ribbon。他的次要性能是负载平衡和服务调用。ribbon 在服务调用是有超时,重试的设置。外部提供默认负载平衡机制。也提供接口不便咱们自定义负载平衡策略。
  • ribbon 的服务调用借助月 RestTemplate,RestTemplate 的负载平衡依赖于 ribbon。两者是相辅相成的一个产品。

Ribbon 原理

  • 下面也说了适宜 RestTemplate 联合应用的。还有 springcloud 提供的 Feign 联合应用的。Ribbon 首先外部在构建一个 http 包下的 ClientHttpRequestInterceptor 拦截器。

@Bean
public LoadBalancerInterceptor ribbonInterceptor(
        LoadBalancerClient loadBalancerClient,
        LoadBalancerRequestFactory requestFactory) {return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}

  • 而后会获取所有被 LoadBalanced 规范的 RestTemplate。遍历所有 RestTemplate 诶个注入咱们 LoadBalancerInterceptor 拦截器。在这个拦截器外部会实现服务列表获取。而后负载平衡。

Ribbon 源码剖析

  • 原理很简略。就是在 RestTemplate 调用之前根据 Ribbon 的能力获取真正须要调用的地址而后交由 RestTemplate 调用。

Ribbon 主动配置


  • 基于 springboot 的 spi 机制咱们可能发现在 springcloud-common 中会加载 LoadBalancerAutoConfiguration 配置类。通过名称咱们大略也能理解到这个类是配置负载平衡的主动配置类。
  • 上面咱们来看看这个类都为咱们筹备了哪些工作。

  • 首先是注入两个成员变量。restTeplates、transformers 两个。
  • restTemplates 就是获取所有被 @LoadBalanced 标注的 restTemplate。筹备为他们注入拦截器
  • LoadBalancerRequestTransformer 类正文Allows applications to transform the load-balanced {@link HttpRequest} given the chosen
  • LoadBalancerRequestTransformer 类正文意思就是容许给指定的申请切换负载平衡策略。这里能力无限有工夫在深挖一下。

LoadBalanced

  • 这里咱们须要先介绍下 Loadbanced 这个注解。为什么退出这个注解咱们就能获取到指定的 RestTemplate 汇合呢。

  • 点开源码发现也没啥货色,就是一个注解示意。然而这个注解不个别。咱们留神到他外部有个元注解 @Qualifier。这个注解是 org.springframework.beans.factory.annotation 包下。理解 Spring 的读者应该晓得这是 spring 注入类的一种形式。对于 spring 注入形式解析咱们独自开篇剖析下。这里咱们只用记住 @Qualifier 会注入雷同属性的 bean.
  • 什么叫雷同属性的 bean。比方咱们下面可能会多个中央注入 RestTemplate。增加 @Loadbanlanced 注解相当于如下注解

  • 而后 @Qualifier 联合 @Autowired 注解就会注入所有 RestTemplate 在 spring 容器中的 bean。且 @Qualifier 中的 value=”” 的。也就是下面两个注解 spring 就会收罗到负载平衡标记的 RestTemplate

LoadBalancerInterceptor

  • 索罗到对象之后上面理所应到应该开始筹备拦截器了


  • 下面生成 RestTemplateCustomizer 对象 springcloud 是通过 lamda 表达式生成的。实际上就是实现 RestTemplateCustomizer 这个接口。外部会将 RestTemplate 对象调用 set 办法将 LoadBancerInterptor 拦截器注入到对象内。在 RestTemplate 执行的时候回先通过过滤器的洗礼。这里留个坑吧。对于 RestTemplate 调用咱们稍后再说。

回到 LoadBalancerAutoConfiguration

  • 咱们持续回到 LoadBalancerAutoConfiguration . 下面咱们晓得两个注入的属性的作用了。在前面咱们看到了 SmartinitializingSingletonLoadBalancerRequestFactory。对于 LoadbalancerRequestFactory 这理论就是个工厂。在结构 LoadBalancerIntercrptor 拦截器的时候须要用到。
  • 重点在 SmartInitializingSingleton 这个类。外面传了一个参数通过 ObjectProvider 封装的。ObjectProvider的作用能够简略了解为 @Autowired。而外部的RestTemplateCustomer 就是咱们上文提到的作用是为了封装RestTemplate
  • customizer.customize就是调用增加拦截器了。
  • 到此 RestTemplate 机会被装在有 Ribbon 实现的拦截器了。当咱们在通过 RestTemplate 调用接口的时候就会有负载平衡的性能了。

RetryTemplate

  • 主动配置之后咱们发现还有两个配置类。认真看看和下面的配置是一样的。然而多了 retry 字眼。这个类次要依赖于 RetryTemplate。是用来重试的。

  • 同样是为 RestTemplate 注入 interceptor。只不过都是 Retry 模式的。

  • RetryLoadBalancerInterceptor拦挡外部实现里实际上就是 RetryTemplate 和 RestTemplate 的区别。

  • 两个拦截器的区别其实就是前者多了一个重试机制。具体重试的策略是通过 LoadBalancedRetryPolicy 设置的。
  • 在 Ribbon 中 RetryTemplate 是须要内部提供的。因为咱们零碎中没有退出所以这里爆红。这里也就没有生成重试的成果。有趣味的读者能够试试。

RestTemplate 联合 Ribbon 调用原理

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor

  • 咱们间接察看 LoadBalancerInterceptor 这个拦截器不难发现。他就是实现了 ClientHttpRequestIntrceptor。这里咱们记住这个接口。在 RestTemplate 调用的时候必定会设计到ClientHttpRequestInterceptor 这个类。

RestTemplate 源码跟踪

  • 咱们还是拿咱们的 order 订单举例。还记的咱们的订单拜访接口吗

http://localhost/order/getpayment/123

getForObject


@Override
@Nullable
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
    HttpMessageConverterExtractor<T> responseExtractor =
            new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
    return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}
  • RestTemplate 办法外部首先通过申请头验证并组装 response。最终会执行 execute 办法。

execute

  • execute 外部是 doExecute. 下面是 doExecute 办法。大略逻辑也很简略。
    ①、构建 request 对象
    ②、发送申请
    ③、解决响应
    ④、返回响应数据
  • 咱们的重点在构建 request 对象上。很显著是在 createRequest 里进行拦截器设置的。最终在 request.execute 执行中进行拦截器链路执行的。

  • RestTemplate 的类构造就是集成 HttpAccessor。外部会保护 ClientHttpRequestFactory 来构建 request 对象。

  • 最终会在 InterceptingClientHttpRequestFactory 这个类中的 createRequest 办法上。
    - 外部就是属性的赋值了。

  • 下面 request.execute 最终就会落在 InterceptingClientHttpRequestFactory.execute 这个能够跟踪下这个类的接口就晓得办法入口了。

  • 在下面咱们可能看到会先判断是否有拦截器,有的话会间接交由拦截器执行。从代码中咱们也可能看出 InterceptingClientHttpRequest 对象会在拦截器局部阻塞。所以这里咱们不难看出下面 Ribbon 实现的 LoadBalancerInterceptor。这个拦截器外部必定须要实现接口的调度。

  • 咱们在 LoadBalancerInteptor 拦截器中看到后面会 Ribbon 找寻地址。而后交由 InterceptorHttpAccess 中内置的 InterceptorClientHttpRequestFactory 工厂来解决申请。没错这个类就是咱们下面申请的货色。货色有回去。这个 createRequest 办法咱们下面曾经看过了,如果外部存在拦截器就会交由拦截器实现。如果没有就会进行转发
  • 这里 InterceptorClientHttpRequestFactory 有点责任链模式的感觉。

Ribbon 负载平衡源码追踪

  • 下面别离介绍了 Ribbon 主动配置、RestTemplate 联合 Ribbon 发送申请两局部源码追踪。当初咱们再把锋芒指向最终 RIbbon 的看家本领负载平衡的源码吧。

  • 下面咱们源码追踪可能晓得在调用之前是通过 LoadBalancerClient.execute 办法实现负载平衡的。LoadBalancerClient 在那个模块注册到 spring 容器里我临时没有找到。心愿理解的读者能够指出。多谢!!!!!


public interface LoadBalancerClient extends ServiceInstanceChooser {<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
    <T> T execute(String serviceId, ServiceInstance serviceInstance,
            LoadBalancerRequest<T> request) throws IOException;
    URI reconstructURI(ServiceInstance instance, URI original);

}

  • 最终 LoadBalancerClient 外部是通过 ILoadBalancer 来实现负载平衡的。说实话笔者这里为了省事并没有落实 ILoadBalancerLoadBalancerClient之间是如何绑定的。
  • 咱们能够看 BaseLoadBalancerILoadBalancer的实现。外面重要的是 chooseServer 这个办法。

public Server chooseServer(Object key) {if (counter == null) {counter = createCounter();
    }
    counter.increment();
    if (rule == null) {return null;} else {
        try {return rule.choose(key);
        } catch (Exception e) {logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
            return null;
        }
    }
}
  • 咱们能够看到外面有个 Counter 对象用于治理数量。最初会有 Irule 对象去实现负载策略。RoundRobbinRule中就是通过 AtomicInteger 原子类操作申请次数在于容器数量取模决定获取哪个容器进行调用的。

public Server choose(ILoadBalancer lb, Object key) {if (lb == null) {log.warn("no load balancer");
        return null;
    }

    Server server = null;
    int count = 0;
    while (server == null && count++ < 10) {List<Server> reachableServers = lb.getReachableServers();
        List<Server> allServers = lb.getAllServers();
        int upCount = reachableServers.size();
        int serverCount = allServers.size();

        if ((upCount == 0) || (serverCount == 0)) {log.warn("No up servers available from load balancer:" + lb);
            return null;
        }

        int nextServerIndex = incrementAndGetModulo(serverCount);
        server = allServers.get(nextServerIndex);

        if (server == null) {
            /* Transient. */
            Thread.yield();
            continue;
        }

        if (server.isAlive() && (server.isReadyToServe())) {return (server);
        }

        // Next.
        server = null;
    }

    if (count >= 10) {
        log.warn("No available alive servers after 10 tries from load balancer:"
                + lb);
    }
    return server;
}

总结

-Ribbon 为咱们提供了很多中内置的负载平衡策略。罕用的就是轮询。如果上述的不满足咱们也能够通过实现 Irule 来自定义策略规定。

  • 只须要在启动类上通过 @RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class) 来指定咱们的服务下负载策略。

正文完
 0