In the second quarter " : the Spring Micro Cloud services technology stack (two) to build high availability Eureka Server, service registration and discovery time", we build a service consumers to use client load balancing, just in time to create
RestTemplate
the object's code on the plus@LoadBalanced
notes, as simple as a step to achieve the client load balancing function, then it is how to achieve it? Starting from the basic source of this article to explore the principles of client load balancing.
Source code analysis
In order for the client with the ability to load balance, we are in the code RestTemplate
to Spring
manage time, will add @LoadBalanced
annotations, as shown in the following code:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
After adding this annotation, then RestTemplate
an instance object to have the ability to load balance, why add this comment after the RestTemplate
instance of the object to have the load balancing capabilities of it? We enter into the annotated source code to find out.
package org.springframework.cloud.client.loadbalancer;
import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
* @author Spencer Gibb
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
From the annotated code view, and general comments it does not make much difference, we can understand it from the comments: "This annotation is used to RestTemplate
mark, to use client load balancing ( LoadBalancerClient
) to configure it. " From the comments, we capture an important keyword that is load balancing the client - LoadBalancerClient
we search in the source code LoadBalancerClient
and found that it is Spring Cloud
an interface defined:
package org.springframework.cloud.client.loadbalancer;
import org.springframework.cloud.client.ServiceInstance;
import java.io.IOException;
import java.net.URI;
public interface LoadBalancerClient extends ServiceInstanceChooser {
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
URI reconstructURI(ServiceInstance instance, URI original);
}
It inherits the ServiceInstanceChooser
interface, which has an abstract method choose
, as follows:
package org.springframework.cloud.client.loadbalancer;
import org.springframework.cloud.client.ServiceInstance;
public interface ServiceInstanceChooser {
ServiceInstance choose(String serviceId);
}
From the two interfaces we can determine the initial load balancer should have four basic methods for the four basic methods, let's look at each:
choose
Method: It is based on the incoming serviceID
, find the service instance corresponding to the load balancer.execute
Methods (a): using the load balancer according to the serviceID
selected content to perform the requested service instance.execute
The method (second): the specified service instances to perform the requested content.reconstructURI
Method: The original incoming service request instances andURI
to construct a suitablehost:port
formURI
. In a distributed service call, we use the service name instead ofhost:port
the form to initiate a call requesting service, such as:String result = restTemplate.getForEntity("http://producer-service/hello/producer", String.class).getBody();
where are using the service name of the service providerproducer-service
to make the call, then this method is to run the service name in the form of calls for the replacementhost:port
form.
We have been emphasizing service instance, do service instance object is stored in the service side of the caller? Obviously not, we service the caller from Eureka Server
pulling a list of examples of services available, and the service list of examples is actually stored in the object services provider associated metadata, with a look ServiceInstance
Source:
package org.springframework.cloud.client;
import java.net.URI;
import java.util.Map;
public interface ServiceInstance {
String getServiceId();
String getHost();
int getPort();
boolean isSecure();
URI getUri();
Map<String, String> getMetadata();
default String getScheme() {
return null;
}
}
It is an interface, we can start to achieve implementation class objects that interface to obtain the service ID
, , HOST
, PORT
, URI
other metadata and whether to enable HTTPS
basic information, from basic information, we can be assembled real call to address based on the service name , and it is noted.
By further reading the code, we are in the interface LoadBalancerClient
package is located org.springframework.cloud.client.loadbalancer
and found two important classes: LoadBalancerInterceptor
and LoadBalancerAutoConfiguration
, seen from the name, is a first class load balancer interceptor, the second is automated configuration class load balancer, which We can be known from the relationship between class diagram below:
We begin to see the LoadBalancerAutoConfiguration
class, to see if there could help us understand what code to achieve load balancing principle.
package org.springframework.cloud.client.loadbalancer;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Auto configuration for Ribbon (client side load balancing).
*
* @author Spencer Gibb
* @author Dave Syer
* @author Will Tran
* @author Gang Li
*/
@Configuration
@ConditionalOnClass(RestTemplate.class)
@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 = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
@Configuration
@ConditionalOnClass(RetryTemplate.class)
public static class RetryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryFactory loadBalancedRetryFactory() {
return new LoadBalancedRetryFactory() {};
}
}
@Configuration
@ConditionalOnClass(RetryTemplate.class)
public static class RetryInterceptorAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
LoadBalancerRequestFactory requestFactory,
LoadBalancedRetryFactory loadBalancedRetryFactory) {
return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
requestFactory, loadBalancedRetryFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
}
More dry goods share, welcome attention to my micro-channel public number: Java Mountain (Micro Signal: itlemon)