Spring cloud upgrade 2020.0 Version x-22 Spring cloud loadbalancer core source code

Code address of this series: https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford

Through the detailed analysis in the previous section, we know that the default configuration class is LoadBalancerClientConfiguration through loadbalancerclientfactory And the microservice name can be obtained through 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);
}

Looking at the configuration class LoadBalancerClientConfiguration, we can find that this class mainly defines two kinds of beans: reactorloadbalancer < serviceinstance > and ServiceInstanceListSupplier.

ReactorLoadBalancer is a load balancer. It mainly provides the function of obtaining and selecting a list of service instances according to the service name.

ReactorLoadBalancer

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

The implementation in the default configuration is:

LoadBalancerClientConfiguration

@Bean
@ConditionalOnMissingBean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(
		Environment environment,
		LoadBalancerClientFactory loadBalancerClientFactory) {
	//Get microservice name
	String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
	//Create roundrobin loadbalancer 
	//Note that LazyProvider is injected here, mainly because the related beans may not have been loaded and registered when registering this Bean. Use LazyProvider instead of directly injecting the required beans to prevent the error that Bean injection cannot be found.
	return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,
			ServiceInstanceListSupplier.class), name);
}

It can be seen that the implementation of ReactorLoadBalancer configured by default is roundrobin loadbalancer. The implementation of this load balancer is very simple. There is an AtomicInteger position of atomic type. Read the list of all service instances from the ServiceInstanceListSupplier, then add 1 to the position atom, model the list size, and return the service instance ServiceInstance at this position in the list.

RoundRobinLoadBalancer

public Mono<Response<ServiceInstance>> choose(Request request) {
    //During injection, the Lazy Provider is injected, and the real Bean, ServiceInstanceListSupplier, is taken out here
	ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
			.getIfAvailable(NoopServiceInstanceListSupplier::new);
			//Get instance list
	return supplier.get(request)
	        .next()
	        //Select an instance from the list
			.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
}

private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
		List<ServiceInstance> serviceInstances) {
	Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
	// If the ServiceInstanceListSupplier also implements SelectedInstanceCallback, execute the following logic for callback. SelectedInstanceCallback is a callback that occurs each time the load balancer selects an instance
	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();
	}
	//Position atom + 1 and take the absolute value
	int pos = Math.abs(this.position.incrementAndGet());
    //Returns an instance of the corresponding subscript
	ServiceInstance instance = instances.get(pos % instances.size());
	return new DefaultResponse(instance);
}

ServiceInstanceListSupplier is the service list provider interface:

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();
	}
}

There are many implementations of ServiceInstanceListSupplier in spring cloud loadbalancer. In the default configuration, the implementation is specified through property configuration. This configuration item is spring cloud. loadbalancer. configurations. For example:

LoadBalancerClientConfiguration

@Bean
@ConditionalOnBean(ReactiveDiscoveryClient.class)
@ConditionalOnMissingBean
//spring.cloud.loadbalancer.configurations is not specified or is default
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.configurations", havingValue = "default",
		matchIfMissing = true)
public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
		ConfigurableApplicationContext context) {
	return ServiceInstanceListSupplier.builder()
	//Providing instances through DiscoveryClient
	.withDiscoveryClient()
	//Enable cache
	.withCaching()
	.build(context);
}

@Bean
@ConditionalOnBean(ReactiveDiscoveryClient.class)
@ConditionalOnMissingBean
//If spring cloud. loadbalancer. Configurations is specified as zone preference
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.configurations", havingValue = "zone-preference")
public ServiceInstanceListSupplier zonePreferenceDiscoveryClientServiceInstanceListSupplier(
		ConfigurableApplicationContext context) {
	return ServiceInstanceListSupplier.builder()
	//Providing instances through DiscoveryClient
	.withDiscoveryClient()
	//Enable features that prefer instances in the same zone
	.withZonePreference()
	//Enable cache
	.withCaching()
			.build(context);
}

As you can see, you can use ServiceInstanceListSupplier Builder () generates a ServiceInstanceListSupplier that officially encapsulates various features. In fact, it can be seen from the underlying implementation that all ServiceInstanceListSupplier implementations are in the proxy mode. For example, for the default configuration, the underlying code is similar to:

return  //Enable service instance cache
        new CachingServiceInstanceListSupplier(
                        //Enable service discovery through discoveryClient
                        new DiscoveryClientServiceInstanceListSupplier(
                                discoveryClient, env
                        )
                , cacheManagerProvider.getIfAvailable()
        );

In addition to the default configuration LoadBalancerClientConfiguration, user-defined configurations are configured through @ LoadBalancerClients and @ LoadBalancerClient This principle is implemented through LoadBalancerClientConfigurationRegistrar. First, let's take a look at how the LoadBalancerClientFactory NamedContextFactory is created:

[LoadBalancerAutoConfiguration]

private final ObjectProvider<List<LoadBalancerClientSpecification>> configurations;

public LoadBalancerAutoConfiguration(ObjectProvider<List<LoadBalancerClientSpecification>> configurations) {
    //Inject the provider of LoadBalancerClientSpecification List
    //When a Bean is created, it is loaded instead of registered
	this.configurations = configurations;
}

@ConditionalOnMissingBean
@Bean
public LoadBalancerClientFactory loadBalancerClientFactory() {
    //Create LoadBalancerClientFactory
	LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory();
    //Read all loadbalancerclientspecifications and set them to the configuration of LoadBalancerClientFactory
	clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList));
	return clientFactory;
}

So, how are these beans created in LoadBalancerClientSpecification? Both @ LoadBalancerClients and @ LoadBalancerClient annotations contain @ Import(LoadBalancerClientConfigurationRegistrar.class). This @ Import loads an ImportBeanDefinitionRegistrar, which is loadbalancerclientconfigurationregistrar The method parameters in importbeandefinitionregister contain annotation metadata and the BeanDefinitionRegistry of the registered Bean. Generally, beans are dynamically registered through BeanDefinitionRegistry through annotation metadata. The implementation here is:

[LoadBalancerClients]

@Configuration(proxyBeanMethods = false)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Documented
@Import(LoadBalancerClientConfigurationRegistrar.class)
public @interface LoadBalancerClients {
    //Multiple loadbalancerclients can be specified
	LoadBalancerClient[] value() default {};
	//Specifies the default configuration for all load balancing configurations
	Class<?>[] defaultConfiguration() default {};
}

[LoadBalancerClient]

@Configuration(proxyBeanMethods = false)
@Import(LoadBalancerClientConfigurationRegistrar.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LoadBalancerClient {
    //Both name and value are microservice names
	@AliasFor("name")
	String value() default "";
	@AliasFor("value")
	String name() default "";
	//Configuration of this microservice
	Class<?>[] configuration() default {};
}

[LoadBalancerClientConfigurationRegistrar]

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    //Get metadata of LoadBalancerClients annotation
	Map<String, Object> attrs = metadata.getAnnotationAttributes(LoadBalancerClients.class.getName(), true);
	if (attrs != null && attrs.containsKey("value")) {
		AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
		//For the value attribute, it is actually a LoadBalancerClient list. For each, a LoadBalancerClientSpecification with a specific microservice name is generated
		for (AnnotationAttributes client : clients) {
			registerClientConfiguration(registry, getClientName(client), client.get("configuration"));
		}
	}
	//If defaultConfiguration is specified, the configuration registered as 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"));
	}
	//Get metadata of LoadBalancerClient annotation
	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) {
    //Initialize the BeanDefinition of LoadBalancerClientSpecification, which is used to register a LoadBalancerClientSpecification Bean
	BeanDefinitionBuilder builder = BeanDefinitionBuilder
			.genericBeanDefinition(LoadBalancerClientSpecification.class);
	//Constructor parameters
	builder.addConstructorArgValue(name);
	builder.addConstructorArgValue(configuration);
	//Register Bean
	registry.registerBeanDefinition(name + ".LoadBalancerClientSpecification", builder.getBeanDefinition());
}

From the code, we can see that by using @ LoadBalancerClients and @ LoadBalancerClient annotations, the corresponding LoadBalancerClientSpecification can be automatically generated, so as to realize the public load balancing configuration or the load balancing configuration of a specific microservice.

In this section, we will analyze the source code of Spring Cloud LoadBalancer in detail to understand its principle. In the next section, we will introduce the functions to be realized by using Spring Cloud LoadBalancer in our project.

Added by alvinho on Thu, 30 Dec 2021 01:00:06 +0200