在 Spring Cloud 中,负载均衡是通过 Spring Cloud LoadBalancer 来实现的,它是一个客户端负载均衡器,允许你在多个服务实例之间分配请求。与传统的负载均衡方式不同,Spring Cloud LoadBalancer 是直接集成在客户端的,它通过负载均衡算法选择合适的服务实例进行请求转发。
Spring Cloud LoadBalancer 提供了一个轻量级的负载均衡功能,它替代了以前的 Ribbon ,并且与Nacos、 Eureka、Consul、Zookeeper 等服务注册发现工具无缝集成。Spring Cloud LoadBalancer 默认支持的负载均衡算法有:轮询、随机、权重等。它与 Spring WebClient 、Feign 等客户端库结合使用,自动为跨服务调用提供负载均衡支持。
使用负载均衡
首先添加添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
使用 @LoadBalanced
注解开启负载均衡,使用 @LoadBalanced
注解 RestTemplate
或 WebClient
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(例如,你在
RestTemplate
或WebClient
中使用的服务名称),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 的入口:
- 客户端代码(例如使用了
@LoadBalanced
的RestTemplate
或WebClient
)会通过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
。