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

[TOC]

之前我们介绍了管理分布式组件注册的服务;eureka、consul、zookeeper、nacos他们都可以实现我们服务的注册于获取。
但是实际我们还是需要我们自己调用最终的客户端获取数据的。

前提概要

  • 上面的服务发现框架都可以使用。consul因为需要保证网络通信正常。而eureka是我们自己注册java服务。所以这里就选择通过eureka来作为我们的服务治理框架。

  • 这里我们就借助我们之前eureka服务搭建整合文章。我们直接用之前那个分支启动eureka服务,一个order服务、两个payment服务。

image
  • 然后还是访问我们localhost/order/payment/123这个接看看响应是否是负载均衡的。
image
  • 这些都是我们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);
}

image
  • 然后会获取所有被LoadBalanced标准的RestTemplate。遍历所有RestTemplate诶个注入我们LoadBalancerInterceptor拦截器。在这个拦截器内部会实现服务列表获取。然后负载均衡。

Ribbon源码分析

  • 原理很简单。就是在RestTemplate调用之前依据Ribbon的能力获取真正需要调用的地址然后交由RestTemplate调用。

Ribbon自动配置

image

image
  • 基于springboot的spi机制我们能够发现在springcloud-common中会加载LoadBalancerAutoConfiguration配置类。通过名称我们大概也能了解到这个类是配置负载均衡的自动配置类。

  • 下面我们来看看这个类都为我们准备了哪些工作。

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

LoadBalanced

  • 这里我们需要先介绍下Loadbanced这个注解。为什么加入这个注解我们就能获取到指定的RestTemplate集合呢。
image
  • 点开源码发现也没啥东西,就是一个注解表示。但是这个注解不一般。我们注意到他内部有个元注解@Qualifier。这个注解是org.springframework.beans.factory.annotation包下。了解Spring的读者应该知道这是spring注入类的一种方式。关于spring注入方式解析我们单独开篇分析下。这里我们只用记住@Qualifier会注入相同属性的bean.

  • 什么叫相同属性的bean。比如我们上面可能会多个地方注入RestTemplate。添加@Loadbanlanced注解相当于如下注解

image
  • 然后@Qualifier结合@Autowired注解就会注入所有RestTemplate在spring容器中的bean。且@Qualifier中的value=""的。也就是上面两个注解spring就会搜罗到负载均衡标记的RestTemplate

LoadBalancerInterceptor

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

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

回到LoadBalancerAutoConfiguration

image
  • 我们继续回到LoadBalancerAutoConfiguration . 上面我们知道两个注入的属性的作用了。在后面我们看到了SmartinitializingSingletonLoadBalancerRequestFactory。关于LoadbalancerRequestFactory这实际就是个工厂。在构造LoadBalancerIntercrptor拦截器的时候需要用到。
  • 重点在SmartInitializingSingleton这个类。里面传了一个参数通过ObjectProvider封装的。ObjectProvider的作用可以简单理解为@Autowired。而内部的RestTemplateCustomer就是我们上文提到的作用是为了封装RestTemplate
  • customizer.customize就是调用添加拦截器了。
  • 到此RestTemplate机会被装在有Ribbon实现的拦截器了。当我们在通过RestTemplate调用接口的时候就会有负载均衡的功能了。

RetryTemplate

image
  • 自动配置之后我们发现还有两个配置类。仔细看看和上面的配置是一样的。但是多了retry字眼。这个类主要依赖于RetryTemplate。是用来重试的。
image
  • 同样是为RestTemplate注入interceptor。只不过都是Retry模式的。
image
  • RetryLoadBalancerInterceptor拦截内部实现里实际上就是RetryTemplate和RestTemplate的区别。
image
  • 两个拦截器的区别其实就是前者多了一个重试机制。具体重试的策略是通过LoadBalancedRetryPolicy设置的。

  • 在Ribbon中RetryTemplate是需要外部提供的。因为我们系统中没有加入所以这里爆红。这里也就没有生成重试的效果。有兴趣的读者可以试试。

RestTemplate结合Ribbon调用原理

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor

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

RestTemplate源码跟踪

  • 我们还是拿我们的order订单举例。还记的我们的订单访问接口吗
    http://localhost/order/getpayment/123

getForObject

image

@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

image
  • execute内部是doExecute.上面是doExecute方法。大概逻辑也很简单。
    ①、构建request对象
    ②、发送请求
    ③、处理响应
    ④、返回响应数据

  • 我们的重点在构建request对象上。很明显是在createRequest里进行拦截器设置的。最终在request.execute执行中进行拦截器链路执行的。

image
image
  • RestTemplate的类结构就是集成HttpAccessor。内部会维护ClientHttpRequestFactory来构建request对象。
image
image
  • 最终会在InterceptingClientHttpRequestFactory这个类中的createRequest方法上。
    -内部就是属性的赋值了。
image
  • 上面request.execute最终就会落在InterceptingClientHttpRequestFactory.execute 这个可以跟踪下这个类的接口就知道方法入口了。
image
  • 在上面我们能够看到会先判断是否有拦截器,有的话会直接交由拦截器执行。从代码中我们也能够看出InterceptingClientHttpRequest对象会在拦截器部分阻塞。所以这里我们不难看出上面Ribbon实现的LoadBalancerInterceptor。这个拦截器内部肯定需要实现接口的调度。
image
image
  • 我们在LoadBalancerInteptor拦截器中看到前面会Ribbon找寻地址。然后交由InterceptorHttpAccess中内置的InterceptorClientHttpRequestFactory工厂来处理请求。没错这个类就是我们上面请求的东西。东西有回去。这个createRequest方法我们上面已经看过了,如果内部存在拦截器就会交由拦截器实现。如果没有就会进行转发

  • 这里InterceptorClientHttpRequestFactory有点责任链模式的感觉。

Ribbon负载均衡源码追踪

  • 上面分别介绍了Ribbon自动配置、RestTemplate结合Ribbon发送请求两部分源码追踪。现在我们再把矛头指向最终RIbbon的看家本领负载均衡的源码吧。
image
  • 上面我们源码追踪能够知道在调用之前是通过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);

}

image
  • 最终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)来指定我们的服务下负载策略。
image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容

  • 夜莺2517阅读 127,718评论 1 9
  • 版本:ios 1.2.1 亮点: 1.app角标可以实时更新天气温度或选择空气质量,建议处女座就不要选了,不然老想...
    我就是沉沉阅读 6,887评论 1 6
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,535评论 28 53
  • 兔子虽然是枚小硕 但学校的硕士四人寝不够 就被分到了博士楼里 两人一间 在学校的最西边 靠山 兔子的室友身体不好 ...
    待业的兔子阅读 2,597评论 2 9