当前位置: 首页 > news >正文

Spring Cloud LoadBalancer (负载均衡)

在 Spring Cloud 中,负载均衡是通过 Spring Cloud LoadBalancer 来实现的,它是一个客户端负载均衡器,允许你在多个服务实例之间分配请求。与传统的负载均衡方式不同,Spring Cloud LoadBalancer 是直接集成在客户端的,它通过负载均衡算法选择合适的服务实例进行请求转发。

Spring Cloud LoadBalancer 提供了一个轻量级的负载均衡功能,它替代了以前的 Ribbon,并且与Nacos、 Eureka、Consul、Zookeeper 等服务注册发现工具无缝集成。Spring Cloud LoadBalancer 默认支持的负载均衡算法有:轮询、随机、权重等。它与 Spring WebClientFeign 等客户端库结合使用,自动为跨服务调用提供负载均衡支持。

使用负载均衡

首先添加添加依赖

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

使用 @LoadBalanced注解开启负载均衡,使用 @LoadBalanced 注解 RestTemplateWebClient Bean。Spring Cloud LoadBalancer 会与 WebClient 配合使用,自动为其提供负载均衡功能。无需显式地配置负载均衡器,只需将服务名称传递给 WebClient,它就会自动选择合适的服务实例。

    @Bean@LoadBalancedpublic WebClient webClient(){return WebClient.create();}public Mono<String> callMyReactiveService() {return webClient().get().uri("http://my-service/api/aaa").retrieve().bodyToMono(String.class);}

源码阅读分析

spring-cloud-loadbalancer.jar包spring.factories文件通过spring spi自动装配类如下

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration,\
org.springframework.cloud.loadbalancer.config.BlockingLoadBalancerClientAutoConfiguration,\
org.springframework.cloud.loadbalancer.config.LoadBalancerCacheAutoConfiguration,\
org.springframework.cloud.loadbalancer.security.OAuth2LoadBalancerClientAutoConfiguration,\
org.springframework.cloud.loadbalancer.config.LoadBalancerStatsAutoConfiguration

这些自动装配类加载了以下负载均衡关键类

LoadBalancerInterceptor

当以服务名进行服务调用时,其核心逻辑还是通过拦截器进行拦截处理。LoadBalancerInterceptor实现了ClientHttpRequestInterceptor接口,当使用 @LoadBalanced 注解的 RestTemplate Bean 发起 HTTP 请求时,LoadBalancerInterceptor 会被 Spring 的拦截器机制拦截,LoadBalancerInterceptor会检查请求的目标 URI 是否使用了服务名(而不是具体的 IP 地址和端口),如果是,则会利用 LoadBalancerClient(或其响应式版本 ReactorLoadBalancer)选择一个该服务的可用实例,并修改请求的 URI,将其替换为所选实例的实际地址。

LoadBalancerInterceptor初始化是在org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration子类LoadBalancerInterceptorConfig中完成的。

LoadBalancerInterceptor#intercept()

	@Overridepublic ClientHttpResponse intercept(final HttpRequest request, final byte[] body,final ClientHttpRequestExecution execution) throws IOException {final URI originalUri = request.getURI();//从请求路径中获取服务名String serviceName = originalUri.getHost();Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);//使用LoadBalancerClient 选择服务实例调用服务return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));}
LoadBalancerClient

获取实际服务处调用 是通过LoadBalancerClient,LoadBalancerClient是一个接口,它定义了客户端负载均衡器的基本操作。你可以把它看作是各种具体负载均衡器实现(例如 Spring Cloud LoadBalancer 中的 ReactorLoadBalancer 或 Netflix Ribbon 中的 RibbonLoadBalancerClient)的通用抽象。

LoadBalancerClient 的主要作用是提供一个统一的、与具体负载均衡器实现无关的 API,供应用程序选择一个可用的服务实例进行通信。 这使得服务消费者能够以一种更抽象的方式使用负载均衡,而无需关心底层负载均衡器的具体实现细节。这里实例类型是BlockingLoadBalancerClient

BlockingLoadBalancerClient#execute()

	@Overridepublic <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {String hint = getHint(serviceId);LoadBalancerRequestAdapter<T, TimedRequestContext> lbRequest = new LoadBalancerRequestAdapter<>(request,buildRequestContext(request, hint));Set<LoadBalancerLifecycle> supportedLifecycleProcessors = getSupportedLifecycleProcessors(serviceId);supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));//根据服务id获取服务实例ServiceInstance serviceInstance = choose(serviceId, lbRequest);if (serviceInstance == null) {supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, new EmptyResponse())));throw new IllegalStateException("No instances available for " + serviceId);}//执行调用服务return execute(serviceId, serviceInstance, lbRequest);}@Overridepublic <T> ServiceInstance choose(String serviceId, Request<T> request) {//从工厂中获取负载均衡器ReactiveLoadBalancerReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId);if (loadBalancer == null) {return null;}//选择具体服务实例调用Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();if (loadBalancerResponse == null) {return null;}return loadBalancerResponse.getServer();}
ReactorLoadBalancer

ReactorLoadBalancer是具体的负载均衡策略实现,ReactorLoadBalancer 会在目标服务的多个实例中选择一个合适的实例。ReactorLoadBalancer的默认初始化是在条件装配类LoadBalancerClientConfiguration会初始化默认的负载策略bean

@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
public class LoadBalancerClientConfiguration {private static final int REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER = 193827465;@Bean@ConditionalOnMissingBeanpublic ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);}//...
}

这里看到默认的负载策略是RoundRobinLoadBalancer,也就是轮询。还有一个内置的负载策略类是RandomLoadBalancer。如果内置的负载策略不能满足要求,可以自定义负载策略,实现ReactorServiceInstanceLoadBalancer接口。

LoadBalancerClientFactory
public class LoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification>implements ReactiveLoadBalancer.Factory<ServiceInstance> {}

LoadBalancerClientFactory是客户端负载均衡核心工厂,它负责创建和管理特定于每个服务 ID 的 ReactorLoadBalancer 实例。你可以将其视为一个负载均衡器客户端的工厂。

其初始化是在org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration自动装配类中

	@ConditionalOnMissingBean@Beanpublic LoadBalancerClientFactory loadBalancerClientFactory(LoadBalancerClientsProperties properties) {LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory(properties);
//将所有的配置 List<LoadBalancerClientSpecification>	设置到Factory中	clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList));return clientFactory;}

LoadBalancerClientFactory 的主要作用:

1. 管理不同服务的负载均衡器配置:

  • Spring Cloud LoadBalancer 允许你为不同的服务配置不同的负载均衡策略或其他自定义行为。LoadBalancerClientFactory 负责根据服务的 ID 加载和应用这些特定的配置。
  • 它会查找与特定服务 ID 关联的 LoadBalancerClientConfiguration Bean,这些配置类定义了该服务应该使用的负载均衡器实现和其他相关的组件。

自定义配置可以通过@LoadBalancerClients和@LoadBalancerClient两个注解进行添加。

@LoadBalancerClients注解引入defaultConfig,已有的两个默认配置是LoadBalancerAutoConfiguration和BlockingLoadBalancerClientAutoConfiguration。

@LoadBalancerClient注解导入自定义的某个服务配置,其有两个属性,value用来指定服务ID名称,configuration用来指定LoadBalancer配置文件

@Configuration
@LoadBalancerClient(value = "stores", configuration = CustomLoadBalancerConfiguration.class)
public class MyConfiguration {@Bean@LoadBalancedpublic WebClient.Builder loadBalancedWebClientBuilder() {return WebClient.builder();}

自定义配置类

public class CustomLoadBalancerConfiguration {//自定义负载均衡器策略为随机@BeanReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),name);}
}

如上,通过WebClient调用 http://stores/** 服务最后会使用随机负载策略进行后端服务调用。

这两个注解会引入LoadBalancerClientConfigurationRegistrar该registrar类,额外添加beanDefine。

LoadBalancerClientConfigurationRegistrar#registerBeanDefinitions()

	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {/**解读所有带@LoadBalancerClients注解的类,这里也就能找到上面的两个AutoConfig类@LoadBalancerClients注解有两个属性(value,defaultConfiguration)*/Map<String, Object> attrs = metadata.getAnnotationAttributes(LoadBalancerClients.class.getName(), true);//上面自动装配两个类上,未配置@LoadBalancerClients属性值,都是默认值//if可以进来 clients 为空数组,if (attrs != null && attrs.containsKey("value")) {AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");for (AnnotationAttributes client : clients) {registerClientConfiguration(registry, getClientName(client), client.get("configuration"));}}if (attrs != null && attrs.containsKey("defaultConfiguration")) {String name;if (metadata.hasEnclosingClass()) {name = "default." + metadata.getEnclosingClassName();}else {name = "default." + metadata.getClassName();}registerClientConfiguration(registry, name, attrs.get("defaultConfiguration"));}//处理所有的LoadBalancerClient注解Map<String, Object> client = metadata.getAnnotationAttributes(LoadBalancerClient.class.getName(), true);String name = getClientName(client);if (name != null) {registerClientConfiguration(registry, name, client.get("configuration"));}}//注册bean定义,bean类型是LoadBalancerClientSpecificationprivate static void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,Object configuration) {BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(LoadBalancerClientSpecification.class);builder.addConstructorArgValue(name);builder.addConstructorArgValue(configuration);registry.registerBeanDefinition(name + ".LoadBalancerClientSpecification", builder.getBeanDefinition());}

最后会将所有注解修饰的类包装成LoadBalancerClientSpecification类型的 bean。List<LoadBalancerClientSpecification>最后会被设置到LoadBalancerClientFactory中。这一步在LoadBalancerAutoConfiguration中实例化LoadBalancerClientFactory完成。

2. 创建和缓存 ReactorLoadBalancer 实例:

  • 对于每个需要进行负载均衡的服务 ID(例如,你在 RestTemplateWebClient 中使用的服务名称),LoadBalancerClientFactory 都会创建一个或多个 ReactorLoadBalancer 实例。

  • 它会缓存这些创建好的 ReactorLoadBalancer 实例,以便在后续需要对同一服务进行负载均衡时能够快速获取,避免重复创建。

    其内部属性Map<String, AnnotationConfigApplicationContext> contexts 用来存储每个服务实例配置容器,key是服务ID。每个服务一个ApplicationContext容器

容器的创建是在LoadBalancerClientFactory的父类NamedContextFactory中完成

NamedContextFactory#createContext()

	protected AnnotationConfigApplicationContext createContext(String name) {AnnotationConfigApplicationContext context;//实例化容器if (this.parent != null) {DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();if (parent instanceof ConfigurableApplicationContext) {beanFactory.setBeanClassLoader(((ConfigurableApplicationContext) parent).getBeanFactory().getBeanClassLoader());}else {beanFactory.setBeanClassLoader(parent.getClassLoader());}context = new AnnotationConfigApplicationContext(beanFactory);context.setClassLoader(this.parent.getClassLoader());}else {context = new AnnotationConfigApplicationContext();}//STEP-A:有没有当前服务对应的显示配置信息,有的化将对应Config类放到容器中if (this.configurations.containsKey(name)) {for (Class<?> configuration : this.configurations.get(name).getConfiguration()) {context.register(configuration);}}//是否有默认的配置for (Map.Entry<String, C> entry : this.configurations.entrySet()) {if (entry.getKey().startsWith("default.")) {for (Class<?> configuration : entry.getValue().getConfiguration()) {context.register(configuration);}}}/**向容器中注册两个默认的配置bean,PropertyPlaceholderAutoConfiguration,这里的defaultConfigType是LoadBalancerClientConfiguration类型,在beanFacotry构造方法初始化的*/context.register(PropertyPlaceholderAutoConfiguration.class, this.defaultConfigType);context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(this.propertySourceName,Collections.<String, Object>singletonMap(this.propertyName, name)));if (this.parent != null) {// Uses Environment from parent as well as beanscontext.setParent(this.parent);}context.setDisplayName(generateDisplayName(name));//refresh 实例化容器beancontext.refresh();return context;}

这里看到每个LoadBalancer对应的容器是手动创建的,也就是在获取的时候就是在第一次调用的时候。

如果有通过@LoadBalancerClient注解导入的服务配置会使用自定义的配置,如果没有使用默认的配置类LoadBalancerClientConfiguration来实例化服务的LoadBalancer。

3. 提供获取特定服务 ReactorLoadBalancer 的入口:

  • 客户端代码(例如使用了 @LoadBalancedRestTemplateWebClient)会通过 LoadBalancerClientFactory 来获取针对特定服务的 ReactorLoadBalancer 实例。
  • LoadBalancerClientFactory 提供了 getInstance(String serviceId) 方法,用于根据服务 ID 获取对应的 ReactorLoadBalancer

LoadBalancerClientFactory#getInstance()

public ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) {//调用父类NamedContextFactory获取实例方法return getInstance(serviceId, ReactorServiceInstanceLoadBalancer.class);
}

NamedContextFactory#getInstance()

	public <T> T getInstance(String name, Class<T> type) {//根据服务名,获取其对应的容器AnnotationConfigApplicationContext context = getContext(name);try {//从当前服务对应容器中获取ReactorServiceInstanceLoadBalancer类型的beanreturn context.getBean(type);}catch (NoSuchBeanDefinitionException e) {// ignore}return null;}

getContext(name)方法会根据当前服务名获取其对应的LoadBalancer容器,从当前Factory内部属性Map<String, AnnotationConfigApplicationContext> contexts中获取,如果不存在调用createContext(String name)方法进行初始化Context。

4. 管理 ServiceInstanceListSupplier 实例:

  • ServiceInstanceListSupplier 接口负责从服务发现组件(如 Eureka、Consul、Nacos)获取指定服务的可用实例列表。
  • LoadBalancerClientFactory 也负责为每个服务 ID 管理 ServiceInstanceListSupplier 实例,并将它们提供给相应的 ReactorLoadBalancer

相关文章:

  • MySQL 与 Elasticsearch 数据一致性方案
  • MTB图像配准算法实现
  • 订阅“科技爱好者周刊”,每周五与你相约科技前沿!
  • python 上海新闻爬虫, 上观新闻 + 腾讯新闻
  • Unity 点击按钮,打开 Windows 文件选择框,并加载图片
  • iOS创建Certificate证书、制作p12证书流程
  • Jsoup与HtmlUnit:两大Java爬虫工具对比解析
  • LeRobot 项目部署运行逻辑(五)——intelrealsense.py/configs.py
  • -bash: /usr/local/mysql/bin/mysqld: No such file or directory
  • uni-app 中的条件编译与跨端兼容
  • Windows 11家庭中文版Docker Desktop汉化全攻略
  • 【Ansible】模块详解
  • Android 项目中配置了多个 maven 仓库,但依赖还是下载失败,除了使用代理,还有其他方法吗?
  • MATLAB制作柱状图与条图:数据可视化的基础利器
  • [6-1] TIM定时中断 江协科技学习笔记(45个知识点)
  • React 第三十七节 Router 中 useOutlet Hook的使用介绍以及注意事项
  • [计算机科学#13]:算法
  • StreamRL:弹性、可扩展、异构的RLHF架构
  • 数据结构 集合类与复杂度
  • 6.01 Python中打开usb相机并进行显示
  • 2025年度上海市住房城乡建设管理委工程系列中级职称评审工作启动
  • 本周看啥|喜欢二次元的观众,去电影院吧
  • 远离军事前线的另一面暗斗:除了“断水”,印度还试图牵制对巴国际援助
  • 新华每日电讯:给“男性妇科病论文”开一剂复方药
  • 习近平同俄罗斯总统普京会谈
  • 外交部回应西班牙未来外交战略:愿与之一道继续深化开放合作