• 【云原生&微服务三】SpringCloud之Ribbon是这样实现负载均衡的(源码剖析@LoadBalanced原理)


    文章目录

    一、前言

    在前面的文章,博主聊了Ribbon如何与SpringCloud、Eureka集成,Ribbon如何自定义负载均衡策略:

    1. 【云原生&微服务一】SpringCloud之Ribbon实现负载均衡详细案例(集成Eureka、Ribbon)
    2. 【云原生&微服务二】SpringCloud之Ribbon自定义负载均衡策略(含Ribbon核心API)

    前面我们学会了怎么使用Ribbon,那么为什么给RestTemplate类上加上了@LoadBalanced注解就可以使用Ribbon的负载均衡?SpringCloud是如何集成Ribbon的?Ribbon如何作用到RestTemplate上的?如何获取到的ILoadBalancer?

    本文就这几个问题展开讨论。

    PS: 文章中涉及到的SpringBoot相关知识点,比如自动装配,移步博主的SpringBoot专栏:Spring Boot系列

    PS2:Ribbon依赖Spring Cloud版本信息如下:

    
        
            
                org.springframework.boot
                spring-boot-dependencies
                2.3.7.RELEASE
                pom
                import
            
            
            
                org.springframework.cloud
                spring-cloud-dependencies
                Hoxton.SR8
                pom
                import
            
            
            
                com.alibaba.cloud
                spring-cloud-alibaba-dependencies
                2.2.5.RELEASE
                pom
                import
            
        
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    下面以请求http://localhost:9090/say/saint为入口进行debug。

    二、@LoadBalanced注解原理

    这里我们就来看看为什么采用@Bean方法将RestTemplate注入到Spring容器时,加上@LoadBalanced注解就可以实现负载均衡
    在这里插入图片描述

    1、找找@LoadBalanced注解在哪

    在这里插入图片描述
    在这里插入图片描述
    @LoadBalanced在org.springframework.cloud.client.loadbalancer包下,属于spring-cloud-commons项目;

    嗯,然后呢?既然是SpringCloud项目,二话不说开找自动装配类XXXAutoConfiguration 或 配置类XXXConfiguration。

    上面说了@LoadBalanced属于spring-cloud-commons项目,找到其jar包下/META-INF/spring.factories文件(为啥找这个文件呢,见SpringBoot自动装配机制原理):
    在这里插入图片描述
    既然是负载均衡,我们从类的命名来推测,锁定AsyncLoadBalancerAutoConfiguration类和LoadBalancerAutoConfiguration类;这两个类选哪个呢,“小公鸡点到谁就是谁”?还是看命名,AsyncLoadBalancerAutoConfiguration中有Async,是异步负载均衡请求的;我们看同步,同步好debug,进一步锁定到LoadBalancerAutoConfiguration类。

    1)@LoadBalanced流程图总述

    在这里插入图片描述

    2)LoadBalancerAutoConfiguration自动装配类

    在这里插入图片描述
    进到类里,我们发现它组合了一个RestTemplate集合,即:我们创建的那个RestTemplate实例(被@LoadBalanced注解标注)会放到这里来!

    • 此处细节涉及到SpringBoot的源码,为了避免偏题,本文仅提供一种思路,不做详细解释;

    在LoadBalancerAutoConfiguration类中会注入一个SmartInitializingSingleton实例;
    在这里插入图片描述SmartInitializingSingleton接口中只有一个afterSingletonsInstantiated()方法;在SpringBoot启动过程中,当RestTemplate实例化完之后,会执行这个方法,做如下操作:

    1. 遍历每个RestTemplate实例,然后再遍历所有的RestTemplateCustomizer对每个RestTemplate实例做定制化操作,即添加拦截器LoadBalancerInterceptor操作。

    1> RestTemplateCustomizer从哪里来的?

    还是在LoadBalancerAutoConfiguration类中会通过@Bean方法注入RestTemplateCustomizer
    在这里插入图片描述

    2> LoadBalancerInterceptor拦截器

    RestTemplateCustomizer所做的定制化就是给RestTemplate添加一个LoadBalancerInterceptor 拦截器;LoadBalancerInterceptorRestTemplateCustomizer都在LoadBalancerAutoConfiguration的静态内部类LoadBalancerInterceptorConfig中,在SpringBoot启动流程中我们知道,同一个类中@Bean方法的加载是从上至下的,所以肯定是LoadBalancerInterceptor先加载到Spring容器中。

    此外,LoadBalancerAutoConfiguration类中还会自动装配一些Retry…相关的类,用于请求重试。

    看到这,我们知道了Ribbon如何作用到RestTemplate上!但是好像还差一点东西,我要通过RestTemplate做一个操作时,入口在哪?

    2、RestTemplate执行请求时的入口

    上面我们说到,针对每一个RestTemplate,都会给其添加一个LoadBalancerInterceptor拦截器,所以我们对RestTemplate执行某个操作时,会被LoadBalancerInterceptor所拦截。
    在这里插入图片描述
    在这里插入图片描述LoadBalancerInterceptor中组合了LoadBalancerClient,通过LoadBalancerClient做负载均衡;

    然而想要将LoadBalancerInterceptor注入到Spring容器,需要先将LoadBalancerClient注入到Spring容器。那么LoadBalancerClient是何时注入的?

    1)LoadBalancerClient何时注入到Spring容器

    去哪找?我去哪找?既然我们集成了netflix-ribbon,找一下以netflix-ribbon命名的jar包;找到spring-cloud-netflix-ribbonjar包;老规矩,SpringClout项目直接就开找自动装配类XxxAutoConfiguration,自动装配类找不到注入Bean,再找配置类XxxConfiguration。
    在这里插入图片描述

    找到RibbonAutoConfiguration类,其中会注入LoadBalancerClient

    在这里插入图片描述
    注意@AutoConfigureAfter注解和@AutoConfigureBefore注解,其表示:RibbonAutoConfiguration类加载要发生在LoadBalancerAutoConfiguration类加载之前、发生在EurekaClientAutoConfiguration类加载之后。

    到这里,LoadBalancerClient实例比定在LoadBalancerAutoConfiguration加载之前已经注入到了Spring容器,我们回到LoadBalancerAutoConfiguration类,看其中注入LoadBalancerInterceptor类到Spring容器的地方;

    2)LoadBalancerInterceptor#intercept()方法拦截请求

    在这里插入图片描述
    LoadBalancerInterceptor类实现ClientHttpRequestInterceptor接口,其中只有一个核心方法:intercept()用于拦截通过RestTemplate执行的请求。
    在这里插入图片描述

    在intercept()方法中,会基于LoadBalancerRequestFactory创建出来一个对RestTemplate请求包装后的请求,并将请求转发给组合的LoadBalancerClient接口的实现类RibbonLoadBalancerClient#execute()方法去执行;

    这里我们也就知道了真正执行RestTemplate请求方法的入口是RibbonLoadBalancerClient#execute()

    下面我们就继续来看RibbonLoadBalancerClient#execute()里面都做了什么?

    3、RibbonLoadBalancerClient执行请求

    从LoadBalancerInterceptor#intercept()方法进入到RibbonLoadBalancerClient#execute()方法代码执行流程如下:
    在这里插入图片描述
    最终进入到RibbonLoadBalancerClient#execute()方法中会做三件事:

    1. 根据服务名从Ribbon自己的Spring子上下文中获取服务名对应的ApplicationContext,进而获取到ILoadBalancer;
    2. 根据负载均衡器ILoadBalancer从Eureka Client获取到的List中选出一个Server。
    3. 拼装真正的请求URI,做HTTP请求调用。

    本文我们重点看一下如何获取到ILoadBalancer?

    1)获取ILoadBalancer流程图总述

    在这里插入图片描述

    2)如何获取到ILoadBalancer?

    进入到#RibbonLoadBalancerClient#getLoadBalancer(String serviceId)方法;

    protected ILoadBalancer getLoadBalancer(String serviceId) {
        // 通过SpringClientFactory来获取对应的LoadBalancer
        return this.clientFactory.getLoadBalancer(serviceId);
    }
    
    • 1
    • 2
    • 3
    • 4

    其将请求交给SpringClientFactory的getLoadBalancer(String)方法处理:
    在这里插入图片描述

    看SpringClientFactory的类图:

    在这里插入图片描述
    SpringClientFactory继承自NamedContextFactory,所以super.getInstance(name, type)方法为NamedContextFactory#getInstance()方法:
    在这里插入图片描述
    SpringClientFactory不是spring包下的,而是spring cloud与ribbon整合代码的包(org.springframework.cloud.netflix.ribbon)下的;

    • 其对spring进行了一定程度上的封装,从spring里面获取bean的入口,都变成了这个spring cloud ribbon自己的SpringClientFactory;
    • 也就是说:对于每个服务名称,都会有一个独立的spring的ApplicationContext容器(体现在NamedContextFactory类的contexts属性中);
      在这里插入图片描述
    • ApplicationContext中包含了自己这个服务的独立的一堆的组件,比如说LoadBalancer;
    • 如果要获取一个服务对应的LoadBalancer,其实就是在自己的那个ApplicationContext里获取LoadBalancer接口类型的实例化Bean;

    在这里插入图片描述
    默认可以通过父类NamedContextFactory的getLoadBalacner()方法获取到ILoadBalancer接口对应的实例ZoneAwareLoadBalancer。即获取到的ILoadBalancer为ZoneAwareLoadBalancer

    1> 为什么默认实例化的ILoadBalancer是ZoneAwareLoadBalancer?

    spring-cloud-netflix-ribbonjar包下找到RibbonClientConfiguration类,RibbonClientConfiguration类中加载了的ILoadBalancer的实例bean --> ZoneAwareLoadBalancer:

    在这里插入图片描述

    默认的LoadBalancer是ZoneAwareLoadBalancer,ZoneAwareLoadBalancer类图如下:
    在这里插入图片描述

    ZoneAwareLoadBalancer继承自DynamicServerListLoadBalancer,DynamicServerListLoadBalancer继承自BaseLoadBalancer。

    三、后续文章

    下一篇文章,我们继续分析:

    • ZoneAwareLoadBalancer(属于ribbon)如何与eureka整合,通过eureka client获取到对应注册表
    • ZoneAwareLoadBalancer如何持续从Eureka中获取最新的注册表信息
    • 如何根据负载均衡器ILoadBalancer从Eureka Client获取到的List中选出一个Server?

    先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

  • 相关阅读:
    动力节点老杜JavaWeb笔记(全)
    1832javaERP管理系统之车间计划管理Myeclipse开发mysql数据库servlet结构java编程计算机网页项目
    基于GIS、RS、VORS模型、CCDM模型、geodetecto、GWR模型集成的生态系统健康的耦合协调分析
    Python二进制序列类型(二)array、struct和memoryview
    Observer
    【python】将python脚本打包成可执行的.exe文件 推荐使用auto-py-to-exe
    更灵活,拓展性更高:回炉重造的DPDK Packet Framework
    GPU提升多分类问题
    pandas连接oracle数据库并拉取表中数据到dataframe中、筛选当前时间(sysdate)到一天之前的所有数据(筛选一天范围数据)
    zynq开发中的设备树
  • 原文地址:https://blog.csdn.net/m0_67392126/article/details/126080715