分布式springcould服务调用Ribbon的负载均衡

[TOC]java

以前咱们介绍了管理分布式组件注册的服务;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 ClientHttpRequestInterceptorspring

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

RestTemplate源码跟踪

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

http://localhost/order/getpayment/123springboot

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)来指定咱们的服务下负载策略。

相关文章
相关标签/搜索