“This is the first day of my participation in the First Challenge 2022. For details: First Challenge 2022”

1 Brief introduction to the @loadBalanced annotation

The code for the @loadBalanced annotation, quite simply, with the @Qualifier annotation, which is used for precise injection by name.

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
Copy the code

About the client load balancer automatic LoadBalancerAutoConfiguration ZhuanPei (can be in spring – cloud – Commons jar package, from finding the meta-inf/spring. Factories)

ConditionalOnClass(restTemplate.class) ConditionalOnBean(loadbalancerClient.class) ConditionalOnBean(loadbalancerClient.class) ConditionalOnBean Automatic configuration takes effect only when the classpath has the RestTemplate and the container has the Bean of the LoadBalancerClient object.

2 classes generated SmartInitializingSingleton Bean objects, LoadBalancerAutoConfiguration. This. RestTemplates refers to all the RestTemplate @ LoadBalanced annotations, which RestTemplateCustomizer, According to the restTemplateCustomizer method below, the RestTemplate is given a loadBalancerInterceptor interceptor.

/** * Auto-configuration for Ribbon (client-side load balancing)@author Spencer Gibb
 * @author Dave Syer
 * @author Will Tran
 * @author Gang Li
 */
@Configuration
// Only for RestTemplate
@ConditionalOnClass(RestTemplate.class)
// The LoadBalancerClient object Bean exists
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();

	@Bean
	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
			final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
		return () -> restTemplateCustomizers.ifAvailable(customizers -> {
            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                for(RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); }}}); }@Autowired(required = false)
	private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

	@Bean
	@ConditionalOnMissingBean
	public LoadBalancerRequestFactory loadBalancerRequestFactory( LoadBalancerClient loadBalancerClient) {
		return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
	}

	@Configuration
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {
		@Bean
		public LoadBalancerInterceptor ribbonInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = newArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; }}}Copy the code

When the RestTemplate initiates a request, the request is intercepted by the LoadBalancerInterceptor, which, according to the intercepting method, is actually a request initiated by the LoadBalancerClient.

LoadBalancerInterceptor class

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

	private LoadBalancerClient loadBalancer;
	private LoadBalancerRequestFactory requestFactory;

	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
		this.loadBalancer = loadBalancer;
		this.requestFactory = requestFactory;
	}

	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
		// for backwards compatibility
		this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
	}

	@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		finalURI originalUri = request.getURI(); String serviceName = originalUri.getHost(); Assert.state(serviceName ! =null."Request URI does not contain a valid hostname: " + originalUri);
		return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution)); }}Copy the code

LoadBalancerClient class

public interface LoadBalancerClient extends ServiceInstanceChooser {

    // Execute the request using the service instance selected from the load balancer
	<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

    // Execute the request using the service instance selected from the load balancer
	<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;

    // Build a fit for the system
	URI reconstructURI(ServiceInstance instance, URI original);
    
    // Inherits the parent's interface
    // Select a corresponding service instance from the client load balancer based on the service name ID passed in
     ServiceInstance choose(String serviceId);
}

Copy the code

RibbonLoadBalancerClient class

Is an implementation class of LoadBalancerClient. In the execute method, the ILoadBalancer object is obtained according to the service ID, and the service instance is obtained by calling getServer(ILoadBalancer,hint), which in turn calls the chooseServer method of loadBalancer.

public class RibbonLoadBalancerClient implements LoadBalancerClient {	
	private SpringClientFactory clientFactory;

	public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
		this.clientFactory = clientFactory;
    }

	@Override
	public ServiceInstance choose(String serviceId) {
	    return choose(serviceId, null);
	}

	/** * New: Select a server using a 'key'. */
	public ServiceInstance choose(String serviceId, Object hint) {
		Server server = getServer(getLoadBalancer(serviceId), hint);
		if (server == null) {
			return null;
		}
		return new RibbonServer(serviceId, server, isSecure(server, serviceId),
				serverIntrospector(serviceId).getMetadata(server));
	}

	@Override
	public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
	    return execute(serviceId, request, null);
	}

	/** * New: Execute a request by selecting server using a 'key'. * The hint will have to be the last parameter to not mess with the `execute(serviceId, ServiceInstance, request)` * method. This somewhat breaks the fluent coding style when using a lambda to define the LoadBalancerRequest. * /
	public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		Server server = getServer(loadBalancer, hint);
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
				serviceId), serverIntrospector(serviceId).getMetadata(server));

		return execute(serviceId, ribbonServer, request);
	}

	@Override
	public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
		Server server = null;
		if(serviceInstance instanceof RibbonServer) {
			server = ((RibbonServer)serviceInstance).getServer();
		}
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}

		RibbonLoadBalancerContext context = this.clientFactory
				.getLoadBalancerContext(serviceId);
		RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

		try {
			T returnVal = request.apply(serviceInstance);
			statsRecorder.recordStats(returnVal);
			return returnVal;
		}
		// catch IOException and rethrow so RestTemplate behaves correctly
		catch (IOException ex) {
			statsRecorder.recordStats(ex);
			throw ex;
		}
		catch (Exception ex) {
			statsRecorder.recordStats(ex);
			ReflectionUtils.rethrowRuntimeException(ex);
		}
		return null;
	}
    
    
    protected Server getServer(ILoadBalancer loadBalancer) {
	    return getServer(loadBalancer, null);
	}

	protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
		if (loadBalancer == null) {
			return null;
		}
		// Use 'default' on a null hint, or just pass it on?
		returnloadBalancer.chooseServer(hint ! =null ? hint : "default");
	}

    protected ILoadBalancer getLoadBalancer(String serviceId) {
		return this.clientFactory.getLoadBalancer(serviceId); }}Copy the code

ILoadBalancer class

Its implementation class, mainly is DynamicServerListLoadBalancer and ZoneAwareLoadBalancer related extension, and is used in the SpringCloud ZoneAwareLoadBalancer class. From the Ribbon in the client configuration class RibbonClientConfiguration ribbonLoadBalancer methods return values.

public interface ILoadBalancer {

	// Add a service instance to the load balancer instance list
	public void addServers(List<Server> newServers);
	
    // By passing in, a service instance can be selected from the load balancer, depending on some policy
	public Server chooseServer(Object key);
	
    // Indicate that a service has been taken offline.
	public void markServerDown(Server server);
	
	@Deprecated
	public List<Server> getServerList(boolean availableOnly);
	
    // Get a list of currently working instances
    public List<Server> getReachableServers(a);

    // Get a list of all instances (normal and offline)
	public List<Server> getAllServers(a);
}

Copy the code

RibbonClientConfiguration class

The ILoadBalancer object is created by default using the ZoneAwareLoadBalancer object.

	@Bean
	@ConditionalOnMissingBean
	public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList
       
         serverList, ServerListFilter
        
          serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater)
        
        {
		if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
			return this.propertiesFactory.get(ILoadBalancer.class, config, name);
		}
		return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
				serverListFilter, serverListUpdater);
	}
Copy the code

Then look at the execution method of Execute in The RibbonLoadBalancerClient class. First, obtain a Server object, and then encapsulate it into a RibbonServer object. Finally, call the overloaded execute method, which calls the Apply method of LoadBalancerRequest to send a request to a specific service instance. The parameter ServiceInstance for the request

ServiceInstance class

His implementation class includes RibbonServer, etc. The interface mainly defines relevant information of some service instances.

public interface ServiceInstance {
    default String getInstanceId(a) {
        return null;
    }

    String getServiceId(a);

    String getHost(a);

    int getPort(a);

    boolean isSecure(a);

    URI getUri(a);

    Map<String, String> getMetadata(a);

    default String getScheme(a) {
        return null; }}Copy the code

LoadBalancerRequest class

This class is an interface that has no associated implementation class. It is added to the LoadBalancerInterceptor interceptor according to the source of the incoming request.

public interface LoadBalancerRequest<T> {
	T apply(ServiceInstance instance) throws Exception;
}
Copy the code

LoadBalancerInterceptor class

Which LoadBalancerRequest create is requestFactory createRequest method.

    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException { URI originalUri = request.getURI(); String serviceName = originalUri.getHost(); Assert.state(serviceName ! =null."Request URI does not contain a valid hostname: " + originalUri);
        return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
    }
Copy the code

LoadBalancerRequestFactory class

	public LoadBalancerRequest<ClientHttpResponse> createRequest(
			final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) {
		return instance -> {
			HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance,
					this.loadBalancer);
			if (this.transformers ! =null) {
				for (LoadBalancerRequestTransformer transformer : this.transformers) { serviceRequest = transformer.transformRequest(serviceRequest, instance); }}return execution.execute(serviceRequest, body);
		};
	}
Copy the code

ServiceRequestWrapper class

This class is an HttpRequestWrapper subclass that overrides the getURI method of the parent class

public class ServiceRequestWrapper extends HttpRequestWrapper {

	private final ServiceInstance instance;

	private final LoadBalancerClient loadBalancer;

	public ServiceRequestWrapper(HttpRequest request, ServiceInstance instance, LoadBalancerClient loadBalancer) {
		super(request);
		this.instance = instance;
		this.loadBalancer = loadBalancer;
	}

	@Override
	public URI getURI(a) {
		URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
		returnuri; }}Copy the code

As for the description of reconstructURI method of RibbonLoadBalancerClient class, the Server object is constructed according to the service instance, and then the URI of the instance is generated by calling reconstructURIWithServer method.

    public URI reconstructURI(ServiceInstance instance, URI original) {
        Assert.notNull(instance, "instance can not be null");
        String serviceId = instance.getServiceId();
        RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
        URI uri;
        Server server;
        if (instance instanceof RibbonLoadBalancerClient.RibbonServer) {
            RibbonLoadBalancerClient.RibbonServer ribbonServer = (RibbonLoadBalancerClient.RibbonServer)instance;
            server = ribbonServer.getServer();
            uri = RibbonUtils.updateToSecureConnectionIfNeeded(original, ribbonServer);
        } else {
            server = new Server(instance.getScheme(), instance.getHost(), instance.getPort());
            IClientConfig clientConfig = this.clientFactory.getClientConfig(serviceId);
            ServerIntrospector serverIntrospector = this.serverIntrospector(serviceId);
            uri = RibbonUtils.updateToSecureConnectionIfNeeded(original, clientConfig, serverIntrospector, server);
        }

        return context.reconstructURIWithServer(server, uri);
    }
Copy the code

LoadBalancerContext class

An instance address for final access is built from the service and a URI object with the service name host.

    public URI reconstructURIWithServer(Server server, URI original) {
        String host = server.getHost();
        int port = server.getPort();
        String scheme = server.getScheme();
        if (host.equals(original.getHost()) && port == original.getPort() && scheme == original.getScheme()) {
            return original;
        } else {
            if (scheme == null) {
                scheme = original.getScheme();
            }

            if (scheme == null) {
                scheme = (String)this.deriveSchemeAndPortFromPartialUri(original).first();
            }

            try {
                StringBuilder sb = new StringBuilder();
                sb.append(scheme).append(": / /");
                if(! Strings.isNullOrEmpty(original.getRawUserInfo())) { sb.append(original.getRawUserInfo()).append("@");
                }

                sb.append(host);
                if (port >= 0) {
                    sb.append(":").append(port);
                }

                sb.append(original.getRawPath());
                if(! Strings.isNullOrEmpty(original.getRawQuery())) { sb.append("?").append(original.getRawQuery());
                }

                if(! Strings.isNullOrEmpty(original.getRawFragment())) { sb.append("#").append(original.getRawFragment());
                }

                URI newURI = new URI(sb.toString());
                return newURI;
            } catch (URISyntaxException var8) {
                throw newRuntimeException(var8); }}}Copy the code

About LoadBalancerRequestFactory LoadBalancerRequest object in the class is to call the execute method of ClientHttpRequestExecution interface.

ClientHttpRequestExecution class

The class has a unique implementation class InterceptingRequestExecution class.

@FunctionalInterface
public interface ClientHttpRequestExecution {
    ClientHttpResponse execute(HttpRequest var1, byte[] var2) throws IOException;
}
Copy the code

InterceptingRequestExecution class

A request object is common through the request factory, where getURI() is the URI that has been overridden to send the client request.

public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
            if (this.iterator.hasNext()) {
                ClientHttpRequestInterceptor nextInterceptor = (ClientHttpRequestInterceptor)this.iterator.next();
                return nextInterceptor.intercept(request, body, this);
            } else{ HttpMethod method = request.getMethod(); Assert.state(method ! =null."No standard HTTP method");
                ClientHttpRequest delegate = InterceptingClientHttpRequest.this.requestFactory.createRequest(request.getURI(), method);
                request.getHeaders().forEach((key, value) -> {
                    delegate.getHeaders().addAll(key, value);
                });
                if (body.length > 0) {
                    if (delegate instanceof StreamingHttpOutputMessage) {
                        StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage)delegate;
                        streamingOutputMessage.setBody((outputStream) -> {
                            StreamUtils.copy(body, outputStream);
                        });
                    } else{ StreamUtils.copy(body, delegate.getBody()); }}returndelegate.execute(); }}Copy the code

2 summary

The @loadBalanced annotation is mainly used to build RestTemplate objects to be load-balanced when sending requests. When this annotation is added, the request is intercepted by the internal LoadBalancerInterceptor interceptor, which converts the requested service name into a specific access address before launching the request.

References:

www.hxstrive.com/article/948…