This article is participating in the “Java Theme Month – Java Brush questions punch card”, see the activity link for details

This project code address: github.com/HashZhang/s…

We used Spring Cloud LoadBalancer as our client LoadBalancer, which is officially recommended by Spring Cloud.

Spring Cloud LoadBalancer background

Spring Cloud LoadBalancer is a client LoadBalancer similar to Ribbon, but Ribbon 2 is not compatible with Ribbon 1 because Ribbon 2 is in maintenance mode. So the Spring Cloud family bucket added Spring Cloud Loadbalancer as a new Loadbalancer in the Spring Cloud Commons project, and made forward compatibility. Even if your project continues to use the Spring Cloud Netflix suite (Ribbon, Eureka, Zuul, Hystrix, etc.) to make your project have these dependencies, you can simply configure Replace ribbon with Spring Cloud LoadBalancer.

Where is the load balancer used?

Internal microservice calls in Spring Cloud default to HTTP requests through the following three apis:

  • RestTemplate: synchronizes HTTP APIS
  • WebClient: Asynchronous and responsive HTTP API
  • Tripartite client encapsulation, such as OpenFeign

If a spring-cloud-loadbalancer dependency is added to the project and the configuration is enabled, loadbalancer features are automatically added to the associated beans.

  • For RestTemplate, it automatically checks all@LoadBalancedThe annotated RestTemplate Bean adds the load balancer feature by adding the Interceptor.
  • For WebClient, it is created automaticallyReactorLoadBalancerExchangeFilterFunctionAnd we can do that by joiningReactorLoadBalancerExchangeFilterFunctionLoad balancer features are added.
  • For three-party clients, we generally don’t need to configure anything extra.

Examples of these uses will be seen in our final test after the upgrade series.

Introduction to the Spring Cloud LoadBalancer architecture

In the previous section we mentioned NamedContextFactory, Spring Cloud LoadBalancer. This mechanism is also used here to implement different microservices using different Spring Cloud LoadBalancer configurations. The relevant core implementation is the @loadBalancerClient and @LoadBalancerClients annotations, And NamedContextFactory. LoadBalancerClientSpecification Specification, The realization of the NamedContextFactory LoadBalancerClientFactory.

After a detailed analysis in the previous section, we can know for LoadBalancerClientConfiguration through LoadBalancerClientFactory know the default configuration class. And get the micro service name by the 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);
}
Copy the code

Check the configuration class LoadBalancerClientConfiguration, we can find this class defines two main kinds of beans, Respectively is ReactorLoadBalancer < ServiceInstance > and ServiceInstanceListSupplier.

ReactorLoadBalancer is a load balancer that obtains and selects from a list of service instances based on the service name.

ReactorLoadBalancer

Mono<Response<T>> choose(Request request);
Copy the code

The implementation in the default configuration is:

LoadBalancerClientConfiguration

@Bean @ConditionalOnMissingBean public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer( Environment environment, LoadBalancerClientFactory LoadBalancerClientFactory) {/ / for micro service name String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); // Create RoundRobinLoadBalancer // Add LazyProvider to RoundRobinLoadBalancer Using LazyProvider instead of injecting the required beans directly prevents the error of not finding the Bean injection. return new RoundRobinLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name); }Copy the code

You can see that the default ReactorLoadBalancer implementation is RoundRobinLoadBalancer. The load balancer implementation is very simple, have an atom type AtomicInteger position, read all of the service from the ServiceInstanceListSupplier instance list, and then to position plus one atom, the list size modulus, Return ServiceInstance, the ServiceInstance for this location in the list.

RoundRobinLoadBalancer

Public Mono<Response<ServiceInstance>> Choose (Request Request) { Namely ServiceInstanceListSupplier ServiceInstanceListSupplier: supplier = serviceInstanceListSupplierProvider .getIfAvailable(NoopServiceInstanceListSupplier::new); 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 ServiceInstanceListSupplier also realized SelectedInstanceCallback, perform the following logic to the callback. SelectedInstanceCallback is the if (Supplier Instanceof SelectedInstanceCallback && callback performed each time the load balancer selects an instance serviceInstanceResponse.hasServer()) { ((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer()); } return serviceInstanceResponse; } private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) { if (instances.isEmpty()) { return new EmptyResponse(); } / / + 1 postion atoms and take absolute value int pos = math.h abs (this. Position. IncrementAndGet ()); ServiceInstance instance = instances. Get (pos % instances. Size ()); return new DefaultResponse(instance); }Copy the code

ServiceInstanceListSupplier is list service 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(); }}Copy the code

Spring – there are a lot of in the cloud – loadbalancer ServiceInstanceListSupplier implementation, through configuration attributes specified in the default configuration, This configuration item is spring. The cloud. The loadbalancer. Configurations. Such as:

LoadBalancerClientConfiguration

@Bean @ConditionalOnBean(ReactiveDiscoveryClient.class) @ConditionalOnMissingBean / / spring. Cloud. Loadbalancer. Configurations is not specified, or for the default @ ConditionalOnProperty (value = "spring.cloud.loadbalancer.configurations", havingValue = "default", matchIfMissing = true) public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier( ConfigurableApplicationContext context) {return ServiceInstanceListSupplier. Builder () / / through DiscoveryClient provide examples .withDiscoveryClient() // Enable caching.withCaching().build(context); } @ Bean @ ConditionalOnBean (ReactiveDiscoveryClient. Class) @ ConditionalOnMissingBean / / if Spring. Cloud. Loadbalancer. Configurations specified zone - preference @ ConditionalOnProperty (value = "spring.cloud.loadbalancer.configurations", havingValue = "zone-preference") public ServiceInstanceListSupplier zonePreferenceDiscoveryClientServiceInstanceListSupplier( ConfigurableApplicationContext context) { return ServiceInstanceListSupplier. Builder () / / through DiscoveryClient provide examples withDiscoveryClient () / / to enable more inclined to the same zone under the characteristics of the instance .withzonepreference () // Enable caching.withCaching().build(context); }Copy the code

As you can see, can pass ServiceInstanceListSupplier. Builder () to generate the official sealed ServiceInstanceListSupplier various features. It can be seen from the underlying implementation, all ServiceInstanceListSupplier implementation is the proxy pattern, for example, the default configuration, similar to the underlying code:

Return / / open service instance cache new CachingServiceInstanceListSupplier (/ / enabled through discoveryClient found new services DiscoveryClientServiceInstanceListSupplier( discoveryClient, env ) , cacheManagerProvider.getIfAvailable() );Copy the code

In addition to the default configuration LoadBalancerClientConfiguration, user configuration custom configuration by @ LoadBalancerClients and @ LoadBalancerClient. This principle is achieved by LoadBalancerClientConfigurationRegistrar. First of all, let’s take a look at this NamedContextFactory LoadBalancerClientFactory is how to create:

[LoadBalancerAutoConfiguration]

private final ObjectProvider<List<LoadBalancerClientSpecification>> configurations; < LoadBalancerClientSpecification public LoadBalancerAutoConfiguration (ObjectProvider < List > > configurations) {/ / injection LoadBalancerClientSpecification List provider / / in the Bean created, load, and is not the time to register this. Configurations = configurations; } @ ConditionalOnMissingBean @ Bean public LoadBalancerClientFactory LoadBalancerClientFactory () {/ / create LoadBalancerClientFactory LoadBalancerClientFactory clientFactory = new LoadBalancerClientFactory(); / / read all LoadBalancerClientSpecification, Set to LoadBalancerClientFactory configuration clientFactory.setConfigurations(this.configurations.getIfAvailable(Collections::emptyList)); return clientFactory; }Copy the code

So, LoadBalancerClientSpecification these beans is how to create? In @ LoadBalancerClients and @ LoadBalancerClient annotations, and contains @ Import (LoadBalancerClientConfigurationRegistrar. Class). The @ Import loading a ImportBeanDefinitionRegistrar, Here is LoadBalancerClientConfigurationRegistrar ImportBeanDefinitionRegistrar annotated method parameter contains metadata inside, And the BeanDefinitionRegistry for registered beans. Generally via annotation metadata, beans are registered dynamically via BeanDefinitionRegistry, where the implementation is:

[LoadBalancerClients]

@Configuration(proxyBeanMethods = false) @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE }) @Documented Public @ @ Import (LoadBalancerClientConfigurationRegistrar. Class) interface LoadBalancerClients {/ / can specify more than one LoadBalancerClient LoadBalancerClient[] value() default {}; // Specify the default for all load balancing configurations Class<? >[] defaultConfiguration() default {}; }Copy the code

[LoadBalancerClient]

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

[LoadBalancerClientConfigurationRegistrar]

@Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// Get LoadBalancerClients metadata 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 a LoadBalancerClient list, For each generated for a specific micro service name LoadBalancerClientSpecification (AnnotationAttributes client: clients) { registerClientConfiguration(registry, getClientName(client), client.get("configuration")); }} // If defaultConfiguration is specified, register the configuration 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 LoadBalancerClient annotated metadata 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, Initialization Object configuration) {/ / LoadBalancerClientSpecification BeanDefinition, Used to register a LoadBalancerClientSpecification Bean BeanDefinitionBuilder builder = BeanDefinitionBuilder .genericBeanDefinition(LoadBalancerClientSpecification.class); / / the constructor parameter builder. AddConstructorArgValue (name); builder.addConstructorArgValue(configuration); / / registered Bean registry. RegisterBeanDefinition (name + ". LoadBalancerClientSpecification ", builder. GetBeanDefinition ()); }Copy the code

As we can see from the code, By using the @ LoadBalancerClients and @ LoadBalancerClient annotations can automatically generate the corresponding LoadBalancerClientSpecification Then the common load balancing configuration or a specific microservice load balancing configuration can be realized.

Wechat search “my programming meow” public account, add the author’s wechat, brush every day, easily improve skills, won a variety of offers: