为何一个@LoadBalanced注解就让RestTemplate拥有负载均衡的能力?【享学Spring Cloud】(上)

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
网络型负载均衡 NLB,每月750个小时 15LCU
EMR Serverless StarRocks,5000CU*H 48000GB*H
简介: 为何一个@LoadBalanced注解就让RestTemplate拥有负载均衡的能力?【享学Spring Cloud】(上)

前言


在Spring Cloud微服务应用体系中,远程调用都应负载均衡。我们在使用RestTemplate作为远程调用客户端的时候,开启负载均衡极其简单:一个@LoadBalanced注解就搞定了。

相信大家大都使用过Ribbon做Client端的负载均衡,也许你有和我一样的感受:Ribbon虽强大但不是特别的好用。我研究了一番,其实根源还是我们对它内部的原理不够了解,导致对一些现象无法给出合理解释,同时也影响了我们对它的定制和扩展。本文就针对此做出梳理,希望大家通过本文也能够对Ribbon有一个较为清晰的理解(本文只解释它@LoadBalanced这一小块内容)。


开启客户端负载均衡只需要一个注解即可,形如这样:


@LoadBalanced // 标注此注解后,RestTemplate就具有了客户端负载均衡能力
@Bean
public RestTemplate restTemplate(){
    return new RestTemplate();
}


说Spring是Java界最优秀、最杰出的重复发明轮子作品一点都不为过。本文就代领你一探究竟,为何开启RestTemplate的负载均衡如此简单。


说明:本文建立在你已经熟练使用RestTemplate,并且了解RestTemplate它相关组件的原理的基础上分析。若对这部分还比较模糊,强行推荐你先参看我前面这篇文章:RestTemplate的使用和原理你都烂熟于胸了吗?【享学Spring MVC】

RibbonAutoConfiguration


这是Spring Boot/Cloud启动Ribbon的入口自动配置类,需要先有个大概的了解:


@Configuration
// 类路径存在com.netflix.client.IClient、RestTemplate等时生效
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class) 
// // 允许在单个类中使用多个@RibbonClient
@RibbonClients 
// 若有Eureka,那就在Eureka配置好后再配置它~~~(如果是别的注册中心呢,ribbon还能玩吗?)
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class })
// 加载配置:ribbon.eager-load --> true的话,那么项目启动的时候就会把Client初始化好,避免第一次惩罚
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class })
public class RibbonAutoConfiguration {
  @Autowired
  private RibbonEagerLoadProperties ribbonEagerLoadProperties;
  // Ribbon的配置文件们~~~~~~~(复杂且重要)
  @Autowired(required = false)
  private List<RibbonClientSpecification> configurations = new ArrayList<>();
  // 特征,FeaturesEndpoint这个端点(`/actuator/features`)会使用它org.springframework.cloud.client.actuator.HasFeatures
  @Bean
  public HasFeatures ribbonFeature() {
    return HasFeatures.namedFeature("Ribbon", Ribbon.class);
  }
  // 它是最为重要的,是一个org.springframework.cloud.context.named.NamedContextFactory  此工厂用于创建命名的Spring容器
  // 这里传入配置文件,每个不同命名空间就会创建一个新的容器(和Feign特别像) 设置当前容器为父容器
  @Bean
  public SpringClientFactory springClientFactory() {
    SpringClientFactory factory = new SpringClientFactory();
    factory.setConfigurations(this.configurations);
    return factory;
  }
  // 这个Bean是关键,若你没定义,就用系统默认提供的Client了~~~
  // 内部使用和持有了SpringClientFactory。。。
  @Bean
  @ConditionalOnMissingBean(LoadBalancerClient.class)
  public LoadBalancerClient loadBalancerClient() {
    return new RibbonLoadBalancerClient(springClientFactory());
  }
  ...
}

这个配置类最重要的是完成了Ribbon相关组件的自动配置,有了LoadBalancerClient才能做负载均衡(这里使用的是它的唯一实现类RibbonLoadBalancerClient


@LoadBalanced


注解本身及其简单(一个属性都木有):


// 所在包是org.springframework.cloud.client.loadbalancer
// 能标注在字段、方法参数、方法上
// JavaDoc上说得很清楚:它只能标注在RestTemplate上才有效
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}

它最大的特点:头上标注有@Qualifier注解,这是它生效的最重要因素之一,本文后半啦我花了大篇幅介绍它的生效时机。

关于@LoadBalanced自动生效的配置,我们需要来到这个自动配置类:LoadBalancerAutoConfiguration


LoadBalancerAutoConfiguration

// Auto-configuration for Ribbon (client-side load balancing).
// 它的负载均衡技术依赖于的是Ribbon组件~
// 它所在的包是:org.springframework.cloud.client.loadbalancer
@Configuration
@ConditionalOnClass(RestTemplate.class) //可见它只对RestTemplate生效
@ConditionalOnBean(LoadBalancerClient.class) // Spring容器内必须存在这个接口的Bean才会生效(参见:RibbonAutoConfiguration)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class) // retry的配置文件
public class LoadBalancerAutoConfiguration {
  // 拿到容器内所有的标注有@LoadBalanced注解的Bean们
  // 注意:必须标注有@LoadBalanced注解的才行
  @LoadBalanced
  @Autowired(required = false)
  private List<RestTemplate> restTemplates = Collections.emptyList(); 
  // LoadBalancerRequestTransformer接口:允许使用者把request + ServiceInstance --> 改造一下
  // Spring内部默认是没有提供任何实现类的(匿名的都木有)
  @Autowired(required = false)
  private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
  // 配置一个匿名的SmartInitializingSingleton 此接口我们应该是熟悉的
  // 它的afterSingletonsInstantiated()方法会在所有的单例Bean初始化完成之后,再调用一个一个的处理BeanName~
  // 本处:使用配置好的所有的RestTemplateCustomizer定制器们,对所有的`RestTemplate`定制处理
  // RestTemplateCustomizer下面有个lambda的实现。若调用者有需要可以书写然后扔进容器里既生效
  // 这种定制器:若你项目中有多个RestTempalte,需要统一处理的话。写一个定制器是个不错的选择
  // (比如统一要放置一个请求拦截器:输出日志之类的)
  @Bean
  public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
    return () -> restTemplateCustomizers.ifAvailable(customizers -> {
      for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
        for (RestTemplateCustomizer customizer : customizers) {
          customizer.customize(restTemplate);
        }
      }
    });
  }
  // 这个工厂用于createRequest()创建出一个LoadBalancerRequest
  // 这个请求里面是包含LoadBalancerClient以及HttpRequest request的
  @Bean
  @ConditionalOnMissingBean
  public LoadBalancerRequestFactory loadBalancerRequestFactory(LoadBalancerClient loadBalancerClient) {
    return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
  }
  // =========到目前为止还和负载均衡没啥关系==========
  // =========接下来的配置才和负载均衡有关(当然上面是基础项)==========
  // 若有Retry的包,就是另外一份配置,和这差不多~~
  @Configuration
  @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
  static class LoadBalancerInterceptorConfig {、
    // 这个Bean的名称叫`loadBalancerClient`,我个人觉得叫`loadBalancerInterceptor`更合适吧(虽然ribbon是唯一实现)
    // 这里直接使用的是requestFactory和Client构建一个拦截器对象
    // LoadBalancerInterceptor可是`ClientHttpRequestInterceptor`,它会介入到http.client里面去
    // LoadBalancerInterceptor也是实现负载均衡的入口,下面详解
    // Tips:这里可没有@ConditionalOnMissingBean哦~~~~
    @Bean
    public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {
      return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
    }
    // 向容器内放入一个RestTemplateCustomizer 定制器
    // 这个定制器的作用上面已经说了:在RestTemplate初始化完成后,应用此定制化器在**所有的实例上**
    // 这个匿名实现的逻辑超级简单:向所有的RestTemplate都塞入一个loadBalancerInterceptor 让其具备有负载均衡的能力
    // Tips:此处有注解@ConditionalOnMissingBean。也就是说如果调用者自己定义过RestTemplateCustomizer类型的Bean,此处是不会执行的
    // 请务必注意这点:容易让你的负载均衡不生效哦~~~~
    @Bean
    @ConditionalOnMissingBean
    public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
      return restTemplate -> {
        List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
        list.add(loadBalancerInterceptor);
        restTemplate.setInterceptors(list);
      };
    }
  }
  ...
}


这段配置代码稍微有点长,我把流程总结为如下几步:


  1. LoadBalancerAutoConfiguration要想生效类路径必须有RestTemplate,以及Spring容器内必须有LoadBalancerClient的实现Bean1. LoadBalancerClient的唯一实现类是:org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient
  2. LoadBalancerInterceptor是个ClientHttpRequestInterceptor客户端请求拦截器。它的作用是在客户端发起请求之前拦截,进而实现客户端的负载均衡
  3. restTemplateCustomizer()返回的匿名定制器RestTemplateCustomizer它用来给所有的RestTemplate加上负载均衡拦截器(需要注意它的@ConditionalOnMissingBean注解~)


不难发现,负载均衡实现的核心就是一个拦截器,就是这个拦截器让一个普通的RestTemplate逆袭成为了一个具有负载均衡功能的请求器

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
20天前
|
Java Spring 容器
如何解决spring EL注解@Value获取值为null的问题
本文探讨了在使用Spring框架时,如何避免`@Value(&quot;${xxx.xxx}&quot;)`注解导致值为null的问题。通过具体示例分析了几种常见错误场景,包括类未交给Spring管理、字段被`static`或`final`修饰以及通过`new`而非依赖注入创建对象等,提出了相应的解决方案,并强调了理解框架原理的重要性。
78 4
|
4天前
|
Java Spring
在使用Spring的`@Value`注解注入属性值时,有一些特殊字符需要注意
【10月更文挑战第9天】在使用Spring的`@Value`注解注入属性值时,需注意一些特殊字符的正确处理方法,包括空格、引号、反斜杠、新行、制表符、逗号、大括号、$、百分号及其他特殊字符。通过适当包裹或转义,确保这些字符能被正确解析和注入。
|
17天前
|
XML Java 数据格式
Spring从入门到入土(bean的一些子标签及注解的使用)
本文详细介绍了Spring框架中Bean的创建和使用,包括使用XML配置文件中的标签和注解来创建和管理Bean,以及如何通过构造器、Setter方法和属性注入来配置Bean。
55 9
Spring从入门到入土(bean的一些子标签及注解的使用)
|
9天前
|
架构师 Java 开发者
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
在40岁老架构师尼恩的读者交流群中,近期多位读者成功获得了知名互联网企业的面试机会,如得物、阿里、滴滴等。然而,面对“Spring Boot自动装配机制”等核心面试题,部分读者因准备不足而未能顺利通过。为此,尼恩团队将系统化梳理和总结这一主题,帮助大家全面提升技术水平,让面试官“爱到不能自已”。
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
|
14天前
|
XML Java 数据库
Spring boot的最全注解
Spring boot的最全注解
|
15天前
|
JSON NoSQL Java
springBoot:jwt&redis&文件操作&常见请求错误代码&参数注解 (九)
该文档涵盖JWT(JSON Web Token)的组成、依赖、工具类创建及拦截器配置,并介绍了Redis的依赖配置与文件操作相关功能,包括文件上传、下载、删除及批量删除的方法。同时,文档还列举了常见的HTTP请求错误代码及其含义,并详细解释了@RequestParam与@PathVariable等参数注解的区别与用法。
|
15天前
|
Java API Spring
springBoot:注解&封装类&异常类&登录实现类 (八)
本文介绍了Spring Boot项目中的一些关键代码片段,包括使用`@PathVariable`绑定路径参数、创建封装类Result和异常处理类GlobalException、定义常量接口Constants、自定义异常ServiceException以及实现用户登录功能。通过这些代码,展示了如何构建RESTful API,处理请求参数,统一返回结果格式,以及全局异常处理等核心功能。
|
2天前
|
存储 Java 数据管理
强大!用 @Audited 注解增强 Spring Boot 应用,打造健壮的数据审计功能
本文深入介绍了如何在Spring Boot应用中使用`@Audited`注解和`spring-data-envers`实现数据审计功能,涵盖从添加依赖、配置实体类到查询审计数据的具体步骤,助力开发人员构建更加透明、合规的应用系统。
|
14天前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
31 0
|
19天前
|
XML Java 数据格式
Java-spring注解的作用
Java-spring注解的作用
15 0