logo头像

学如逆水行舟,不进则退!!!

文章目录

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

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

上一篇
坚持原创技术分享,您的支持将鼓励我继续创作!

评论系统未开启,无法评论!