
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);
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.
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.
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:
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.