spring boot loadbalancer 加载过程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring boot loadbalancer 加载过程相关的知识,希望对你有一定的参考价值。

参考技术A open feign3.0开始就不支持Ribbon了
所以只能用loadbalancer
spring-cloud-loadbalancer 官网文档

首先说明一下
spring cloud loadbalancer 是可以单独使用的
但通常我们会和consul等注册中心一起使用
这样我们就不用写死配置了(配置集群里面有哪些服务)
还有这个版本与OpenFeign 和 consul配合使用是不需要做任何配置的

并且spring-cloud-loadbalancer 是 spring-cloud-commons的一个子项目

spring.factories

功能:

首先在spring-cloud-commons包下也有个叫LoadBalancerAutoConfiguration的配置类,这个配置类会在它前面执行
功能:

请先读一下这篇文章: spring boot open feign 客户端调用过程
会知道OpenFeign发起请求前会调用BlockingLoadBalancerClient.choose选择一个服务端
上面也提到了BlockingLoadBalancerClient 它的加载过程。
接下来看看它的 实现

从 loadBalancerClientFactory 里取得服务列表
但我们提到了,我们并没有主动的创建服务器列表。
而是通过consul取得的,那么是什么时候取得的呢?
读一下: spring boot consul 客户端加载过程
我们能知道 consul是在web服务器启动完成后,才向注册中心发起注册的
也就是在这之前LoadBalancerClientFactory 一直是空的

所以是调用choose方法的时候才去拉取consul 上的服务器列表
所以我们看下:ReactiveLoadBalancer.choose
它是一个接口,看一下它有哪些实现

共两个实现:
1,RoundRobinLoadBalancer
2,RandomLoadBalancer
这里的ReactiveLoadBalancer实际上是RoundRobinLoadBalancer(默认的)

但我们在
spring-cloud-loadbalancer的spring.factories
并没有发现RoundRobinLoadBalancer的初始化
不过我们发现它是从
loadBalancerClientFactory里取出来的

继承自:NamedContextFactory:真正创建ReactiveLoadBalancer对象
实现了:ReactiveLoadBalancer.Factory。看名字就知道,它就是用来创建ReactiveLoadBalancer 的,但它是委托给了NamedContextFactory 来做的

看一下NamedContextFactory是怎么做的

可以看到
先getContext(name)
没有的化就createContext

然后就是这句

defaultConfigType是LoadBalancerClientConfiguration

context将LoadBalancerClientConfiguration注册到上下文环境中
然后context.refresh();
LoadBalancerClientConfiguration的对象就创建出来了(spring.fatories里是没有它的)

可以看到 这里创建了 RoundRobinLoadBalancer

为什么要用NamedContextFactory这样创建
因为,道理很简单,spring-cloud-balancer要为每一个rpc客户端创建一个自己的上下文环境包含自己的配置

用服务名去取,这个serviceId在OpenFeign, Loadbalancer,Consul里是一致的

然后这里还创建了ServiceInstanceListSupplier
看这句,创建这个类的时候用到了DiscoveryClient,还把它们缓存了起来
这里context:ConfigurableApplicationContext包含ConsuDiscoveryClient

这里用到了BiFunciton,大家可以自己去查一下怎么用

DiscoveryClientServiceInstanceListSupplier
这里还用到了 Flux
大家可以自己去看

主要是这句

这里的delegate就是ConsulDiscoveryClient

然后就回到RoundRobinLoadBalancer
这个类其实很简单,就是取到服务器列表,然后一个简单的轮询

SpringCloud升级之路-2020.0.x - 6.使用 Spring Cloud LoadBalancer

本项目代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford

我们使用 Spring Cloud 官方推荐的 Spring Cloud LoadBalancer 作为我们的客户端负载均衡器。

Spring Cloud LoadBalancer背景

Spring Cloud LoadBalancer是一个客户端负载均衡器,类似于Ribbon,但是由于Ribbon已经进入维护模式,并且Ribbon 2并不与Ribbon 1相互兼容,所以Spring Cloud全家桶在Spring Cloud Commons项目中,添加了Spring cloud Loadbalancer作为新的负载均衡器,并且做了向前兼容,就算你的项目中继续用 Spring Cloud Netflix 套装(包括Ribbon,Eureka,Zuul,Hystrix等等)让你的项目中有这些依赖,你也可以通过简单的配置,把ribbon替换成Spring Cloud LoadBalancer。

负载均衡器在哪里使用?

Spring Cloud 中内部微服务调用默认是 http 请求,主要通过下面三种 API:

  • RestTemplate:同步 http API
  • WebClient:异步响应式 http API
  • 三方客户端封装,例如 openfeign

如果项目中加入了 spring-cloud-loadbalancer 的依赖并且配置启用了,那么会自动在相关的 Bean 中加入负载均衡器的特性。

  • 对于 RestTemplate,会自动对所有 @LoadBalanced 注解修饰的 RestTemplate Bean 增加 Interceptor 从而加上了负载均衡器的特性。
  • 对于 WebClient,会自动创建 ReactorLoadBalancerExchangeFilterFunction,我们可以通过加入ReactorLoadBalancerExchangeFilterFunction会加入负载均衡器的特性。
  • 对于三方客户端,一般不需要我们额外配置什么。

这些使用的示例,会在我们系列升级完最后的测试部分看到。

Spring Cloud LoadBalancer 结构简介

上一节我们提到了 NamedContextFactory,Spring Cloud LoadBalancer 这里也是使用了这个机制实现了不同微服务使用不同的 Spring Cloud LoadBalancer 配置。相关核心实现是 @LoadBalancerClient@LoadBalancerClients 这两个注解,以及 NamedContextFactory.Specification 的实现 LoadBalancerClientSpecificationNamedContextFactory 的实现 LoadBalancerClientFactory

经过上一节的详细分析,我们知道可以通过 LoadBalancerClientFactory 知道默认配置类LoadBalancerClientConfiguration. 并且获取微服务名称可以通过 environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);

LoadBalancerClientFactory

public static final String NAMESPACE = "loadbalancer";
public static final String PROPERTY_NAME = NAMESPACE + ".client.name";
public LoadBalancerClientFactory() {
	super(LoadBalancerClientConfiguration.class, NAMESPACE, PROPERTY_NAME);
}

查看配置类 LoadBalancerClientConfiguration,我们可以发现这个类主要定义两种 Bean,分别是 ReactorLoadBalancer<ServiceInstance>ServiceInstanceListSupplier

ReactorLoadBalancer 是负载均衡器,主要提供根据服务名称获取服务实例列表并从从中选择的功能。

ReactorLoadBalancer

Mono<Response<T>> choose(Request request);

在默认配置中的实现是:

LoadBalancerClientConfiguration

@Bean
@ConditionalOnMissingBean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(
		Environment environment,
		LoadBalancerClientFactory loadBalancerClientFactory) {
	//获取微服务名称
	String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
	//创建 RoundRobinLoadBalancer 
	//注意这里注入的是 LazyProvider,这主要因为在注册这个 Bean 的时候相关的 Bean 可能还没有被加载注册,利用 LazyProvider 而不是直接注入所需的 Bean 防止报找不到 Bean 注入的错误。
	return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,
			ServiceInstanceListSupplier.class), name);
}

可以看出,默认配置的 ReactorLoadBalancer 实现是 RoundRobinLoadBalancer。这个负载均衡器实现很简单,有一个原子类型的 AtomicInteger position,从 ServiceInstanceListSupplier 中读取所有的服务实例列表,然后对于 position 原子加1,对列表大小取模,返回列表中这个位置的服务实例 ServiceInstance

RoundRobinLoadBalancer

public Mono<Response<ServiceInstance>> choose(Request request) {
    //注入的时候注入的是 Lazy Provider,这里取出真正的 Bean,也就是 ServiceInstanceListSupplier
	ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
			.getIfAvailable(NoopServiceInstanceListSupplier::new);
			//获取实例列表
	return supplier.get(request)
	        .next()
	        //从列表中选择一个实例
			.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
}

private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
		List<ServiceInstance> serviceInstances) {
	Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
	// 如果 ServiceInstanceListSupplier 也实现了 SelectedInstanceCallback,则执行下面的逻辑进行回调。SelectedInstanceCallback 就是每次负载均衡器选择实例之后进行的回调
	if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
		((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
	}
	return serviceInstanceResponse;
}

private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
	if (instances.isEmpty()) {
		return new EmptyResponse();
	}
	//postion 原子 +1 并取绝对值
	int pos = Math.abs(this.position.incrementAndGet());
    //返回对应下标的实例
	ServiceInstance instance = instances.get(pos % instances.size());
	return new DefaultResponse(instance);
}

ServiceInstanceListSupplier 是服务列表提供者接口:

ServiceInstanceListSupplier

public interface ServiceInstanceListSupplier extends Supplier<Flux<List<ServiceInstance>>> {
	String getServiceId();
	default Flux<List<ServiceInstance>> get(Request request) {
		return get();
	}
	static ServiceInstanceListSupplierBuilder builder() {
		return new ServiceInstanceListSupplierBuilder();
	}
}

spring-cloud-loadbalancer 中有很多 ServiceInstanceListSupplier 的实现,在默认配置中是通过属性配置指定实现的,这个配置项是spring.cloud.loadbalancer.configurations。例如:

LoadBalancerClientConfiguration

@Bean
@ConditionalOnBean(ReactiveDiscoveryClient.class)
@ConditionalOnMissingBean
//spring.cloud.loadbalancer.configurations 未指定或者为 default
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.configurations", havingValue = "default",
		matchIfMissing = true)
public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
		ConfigurableApplicationContext context) {
	return ServiceInstanceListSupplier.builder()
	//通过 DiscoveryClient 提供实例
	.withDiscoveryClient()
	//开启缓存
	.withCaching()
	.build(context);
}

@Bean
@ConditionalOnBean(ReactiveDiscoveryClient.class)
@ConditionalOnMissingBean
//如果 spring.cloud.loadbalancer.configurations 指定为 zone-preference
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.configurations", havingValue = "zone-preference")
public ServiceInstanceListSupplier zonePreferenceDiscoveryClientServiceInstanceListSupplier(
		ConfigurableApplicationContext context) {
	return ServiceInstanceListSupplier.builder()
	//通过 DiscoveryClient 提供实例
	.withDiscoveryClient()
	//启用更倾向于同一个 zone 下实例的特性
	.withZonePreference()
	//开启缓存
	.withCaching()
			.build(context);
}

可以看到,可以通过 ServiceInstanceListSupplier.builder() 生成官方封装好各种特性的 ServiceInstanceListSupplier。其实从底层实现可以看出,所有的 ServiceInstanceListSupplier 实现都是代理模式,例如对于默认配置,底层代码近似于:

return  //开启服务实例缓存
        new CachingServiceInstanceListSupplier(
                        //启用通过 discoveryClient 的服务发现
                        new DiscoveryClientServiceInstanceListSupplier(
                                discoveryClient, env
                        )
                , cacheManagerProvider.getIfAvailable()
        );

除了默认配置 LoadBalancerClientConfiguration,用户配置自定义配置则是通过 @LoadBalancerClients@LoadBalancerClient.这个原理是通过 LoadBalancerClientConfigurationRegistrar 实现的。首先,我们来看一下 LoadBalancerClientFactory 这个 NamedContextFactory 是如何创建的:

[LoadBalancerAutoConfiguration]

private final ObjectProvider<List<LoadBalancerClientSpecification>> configurations;

public LoadBalancerAutoConfiguration(ObjectProvider<List<LoadBalancerClientSpecification>> configurations) {
    //注入 LoadBalancerClientSpecification List 的 provider
    //在 Bean 创建的时候,进行载入,而不是注册的时候
	this.configurations = configurations;
}

@ConditionalOnMissingBean
@Bean
public LoadBalancerClientFactory loadBalancerClientFactory() {
    //创建 LoadBalancerClientFactory
	LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory();
    //读取所有的 LoadBalancerClientSpecification,设置为 LoadBalancerClientFactory 的配置
	clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList));
	return clientFactory;
}

那么,LoadBalancerClientSpecification 这些 Bean 是怎么创建的呢?在 @LoadBalancerClients@LoadBalancerClient 注解中,都包含 @Import(LoadBalancerClientConfigurationRegistrar.class)。这个 @Import 加载一个 ImportBeanDefinitionRegistrar,这里是 LoadBalancerClientConfigurationRegistrar. ImportBeanDefinitionRegistrar里面的方法参数包含注解元数据,以及注册 Bean 的 BeanDefinitionRegistry。一般通过注解元数据,动态通过 BeanDefinitionRegistry 注册 Bean,在这里的实现是:

[LoadBalancerClients]

@Configuration(proxyBeanMethods = false)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Documented
@Import(LoadBalancerClientConfigurationRegistrar.class)
public @interface LoadBalancerClients {
    //可以指定多个 LoadBalancerClient
	LoadBalancerClient[] value() default {};
	//指定所有的负载均衡配置的默认配置
	Class<?>[] defaultConfiguration() default {};
}

[LoadBalancerClient]

@Configuration(proxyBeanMethods = false)
@Import(LoadBalancerClientConfigurationRegistrar.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoadBalancerClient {
    //name 和 value 都是微服务名称
	@AliasFor("name")
	String value() default "";
	@AliasFor("value")
	String name() default "";
	//这个微服务的配置
	Class<?>[] configuration() default {};
}

[LoadBalancerClientConfigurationRegistrar]

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    //获取 LoadBalancerClients 注解的元数据
	Map<String, Object> attrs = metadata.getAnnotationAttributes(LoadBalancerClients.class.getName(), true);
	if (attrs != null && attrs.containsKey("value")) {
		AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
		//对于 value 属性,其实就是一个 LoadBalancerClient 列表,对于每个生成一个特定微服务名字的  LoadBalancerClientSpecification
		for (AnnotationAttributes client : clients) {
			registerClientConfiguration(registry, getClientName(client), client.get("configuration"));
		}
	}
	//如果指定了 defaultConfiguration,则注册为 default 的配置
	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"));
	}
}

private static void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
    //初始化 LoadBalancerClientSpecification 的 BeanDefinition,用于注册一个 LoadBalancerClientSpecification Bean
	BeanDefinitionBuilder builder = BeanDefinitionBuilder
			.genericBeanDefinition(LoadBalancerClientSpecification.class);
	//构造器参数
	builder.addConstructorArgValue(name);
	builder.addConstructorArgValue(configuration);
	//注册 Bean
	registry.registerBeanDefinition(name + ".LoadBalancerClientSpecification", builder.getBeanDefinition());
}

从代码中我们可以看出,通过使用 @LoadBalancerClients@LoadBalancerClient 注解可以自动生成对应的 LoadBalancerClientSpecification 进而实现公共负载均衡配置或者特定某个微服务的负载均衡配置。

微信搜索“我的编程喵”关注公众号,加作者微信,每日一刷,轻松提升技术,斩获各种offer

image

以上是关于spring boot loadbalancer 加载过程的主要内容,如果未能解决你的问题,请参考以下文章

spring boot 启动类 添加组件

spring boot Open Feign 客户端加载过程

Spring Cloud Loadbalancer 修改默认缓存为Caffeine,修改微服务启动关于Loadbalancer的WARN

Spring Cloud Loadbalancer 修改默认缓存为Caffeine,修改微服务启动关于Loadbalancer的WARN

Spring Cloud 使用Spring Cloud Loadbalancer访问服务地址

Spring Netflix LoadBalancing 规则比较