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,它就会自动选择合适的服务实例。

java 复制代码
    @Bean
    @LoadBalanced
    public 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自动装配类如下

properties 复制代码
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()

java 复制代码
	@Override
	public 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()

java 复制代码
	@Override
	public <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);
	}

	@Override
	public <T> ServiceInstance choose(String serviceId, Request<T> request) {
      //从工厂中获取负载均衡器ReactiveLoadBalancer
		ReactiveLoadBalancer<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

java 复制代码
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
public class LoadBalancerClientConfiguration {

	private static final int REACTIVE_SERVICE_INSTANCE_SUPPLIER_ORDER = 193827465;

	@Bean
	@ConditionalOnMissingBean
	public 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
java 复制代码
public class LoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification>
		implements ReactiveLoadBalancer.Factory<ServiceInstance> {
          
		}

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

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

java 复制代码
	@ConditionalOnMissingBean
	@Bean
	public 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配置文件

java 复制代码
@Configuration
@LoadBalancerClient(value = "stores", configuration = CustomLoadBalancerConfiguration.class)
public class MyConfiguration {

    @Bean
    @LoadBalanced
    public WebClient.Builder loadBalancedWebClientBuilder() {
        return WebClient.builder();
    }

自定义配置类

java 复制代码
public class CustomLoadBalancerConfiguration {
	//自定义负载均衡器策略为随机
    @Bean
    ReactorLoadBalancer<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()

java 复制代码
	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类型是LoadBalancerClientSpecification
	private 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()

java 复制代码
	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 beans
			context.setParent(this.parent);
		}
		context.setDisplayName(generateDisplayName(name));
       //refresh 实例化容器bean
		context.refresh();
		return context;
	}

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

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

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

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

LoadBalancerClientFactory#getInstance()

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

NamedContextFactory#getInstance()

java 复制代码
	public <T> T getInstance(String name, Class<T> type) {
        //根据服务名,获取其对应的容器
		AnnotationConfigApplicationContext context = getContext(name);
		try {
          //从当前服务对应容器中获取ReactorServiceInstanceLoadBalancer类型的bean
			return 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
相关推荐
magic 24512 分钟前
第2章——springboot核心机制
java·spring boot·spring
二十雨辰1 小时前
[Spring]-认识Spring
java·数据库·spring
胡斌附体3 小时前
微服务中 本地启动 springboot 无法找到nacos配置 启动报错
java·spring boot·微服务·yml·naocs yml
cooldream20094 小时前
深入理解负载均衡:传输层与应用层的原理与实战
运维·负载均衡·系统架构师
hello_ejb35 小时前
聊聊Spring AI autoconfigure模块的拆分
java·人工智能·spring
搞不懂语言的程序员5 小时前
Consumer Group的作用是什么?Rebalance的触发条件有哪些? (实现消费者负载均衡;消费者加入/离开、订阅Topic变化等)
运维·负载均衡
what_20186 小时前
集群/微服务/分布式
运维·微服务·架构
hello_ejb39 小时前
聊聊Spring AI Alibaba的SentenceSplitter
人工智能·python·spring
金斗潼关10 小时前
SpringCloud GateWay网关
java·spring cloud·gateway