Series of articles:
SpringCloud source series (1) – registry initialization for Eureka
SpringCloud source code series (2) – Registry Eureka service registration, renewal
SpringCloud source code series (3) – Registry Eureka crawl registry
SpringCloud source code series (4) – Registry Eureka service offline, failure, self-protection mechanism
SpringCloud source series (5) – Registry EurekaServer cluster for Eureka
SpringCloud source Code Series (6) – Summary of the Registry Eureka
Load balancing
RestTemplate
In exploring the eureka source code, we define a RestTemplate with the @loadBalanced tag in the Demo-Consumer service, The RestTemplate is then used to invoke the remote service Demo-Producer in the form of the service name. The request is then polled on both Demo-Producer instances.
RestTemplate is a network request framework in Spring Resources that accesses third-party RESTful apis. RestTemplate is used to consume REST services, so the main methods of RestTemplate are closely related to REST Http methods, such as HEAD, GET, POST, PUT, DELETE, and OPTIONS. These methods correspond to headForHeaders(), getForObject(), postForObject(), PUT (), and delete() in the RestTemplate class.
The RestTemplate itself is not LoadBalanced. If the @loadbalanced flag is not used, an error will be reported if the RestTemplate is called using the service name. With the @loadBalanced flag, the REST method that calls the RestTemplate is routed to a service instance using a load balancing policy. The Ribbon is responsible for load balancing. We’ll see how @loadbalanced makes the RestTemplate LoadBalanced later.
@SpringBootApplication
public class ConsumerApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate(a) {
return new RestTemplate();
}
public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); }}@RestController
public class DemoController {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private RestTemplate restTemplate;
@GetMapping("/v1/id")
public ResponseEntity<String> getId(a) {
// called by the service name
ResponseEntity<String> result = restTemplate.getForEntity("http://demo-producer/v1/uuid", String.class);
String uuid = result.getBody();
logger.info("request id: {}", uuid);
returnResponseEntity.ok(uuid); }}Copy the code
Ribbon and load balancing
1. Load balancing
Load balancing refers to the distribution of load among multiple execution units. Load balancing can be divided into centralized load balancing and in-process load balancing:
Centralized load balancing
: stands between the Internet and the execution unit, and is responsible for forwarding network requests to the execution units, such as Nginx and F5. Centralized load balancing can also be called server load balancing.Intra-process load balancing
: integrates load balancing logic into the client, which maintains a list of instances of service providers, typically retrieved from a registry such as Eureka. With a list of instances, you can achieve load balancing by allocating requests to multiple service providers through a load balancing policy. In-process load balancing is also known as client load balancing.
The Ribbon is a client load balancer that controls HTTP and TCP client load balancing behavior. The Ribbon is an open source load balancing component of Netflix that has been integrated into the SpringCloud ecosystem. It is an indispensable component in the SpringCloud ecosystem. Without it, the service cannot scale horizontally.
2. Ribbon Module
The Ribbon has many sub-modules. According to the official documentation, Netflix’s Ribbon is mainly used in production environment as follows:
ribbon-loadbalancer
: Load balancer API that can be used independently or with other modules.ribbon-eureka
The Ribbon combines the Eureka API to provide dynamic service registry information for load balancers.ribbon-core
: Core API of the Ribbon
3. Integration of SpringCloud and Ribbon
Similar to eureka’s integration into Spring Cloud, Spring Cloud provides a corresponding spring-Cloud-starter-Netflix-Eureka-client (server) dependency package, The ribbon is integrated into spring-Cloud-starter-Netflix-ribbon. There is no need to introduce the ribbon dependency package separately. Spring-cloud-starter-netflix-eureka-client already relies on spring-cloud-starter-Netflix-ribbon. Therefore, we introduced spring-cloud-starter-Netflix-Eureka-client to make use of the Ribbon functions.
4. Ribbon is integrated with RestTemplate
In Spring Cloud’s microservice system, Ribbon serves as a load balancer for service consumers. It can be used in two ways, one is combined with RestTemplate and the other is combined with Feign. Now that we’ve demonstrated the use of RestTemplate with load balancing, here’s a diagram to look at remote calls to RestTemplate based on the Ribbon.
RestTemplate Load balancing
@ LoadBalanced annotations
Take RestTemplate as a starting point to look at the core principle of load balancing in the Ribbon. So let’s first look at how the @loadbalanced annotation makes the RestTemplate LoadBalanced.
First look at the @loadBalanced annotation definition, we get the following information:
- This annotation uses
@Qualifier
The LoadBalanced annotation bean object can be injected elsewhere. - As you can see from the comments, the RestTemplate or WebClient of the @loadBalanced tag will use it
LoadBalancerClient
To configure the bean object.
/** * Annotation to mark a RestTemplate or WebClient bean to be configured to use a LoadBalancerClient. */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
Copy the code
Note that @loadBalanced is under the loadBalancer package under the Spring-Cloud-Commons module.
RestTemplate Automatic load balancing configuration
In @ LoadBalanced with package, have a LoadBalancerAutoConfiguration automation configuration class, as can be seen from the comments also, this is the client load balance Ribbon automation configuration class.
From this automated configuration class you can get the following information:
- The first step is to have a dependency and definition for the RestTemplate
LoadBalancerClient
Object, which corresponds to the RestTemplate LoadBalancerClient configuration. - You can then see that the class is injected with a
RestTemplate object with the @loadBalanced annotation
Is to increase the load balancing capability for these objects. - from
SmartInitializingSingleton
After the bean initialization is complete, use theRestTemplateCustomizer
Customize the RestTemplate. - Further down, you can see that the RestTemplateCustomizer is actually added to the RestTemplate
LoadBalancerInterceptor
This interceptor. - The LoadBalancerInterceptor build requires that
LoadBalancerClient and LoadBalancerRequestFactory
, LoadBalancerRequestFactory through LoadBalancerClient and LoadBalancerRequestTransformer constructed.
/** * Auto-configuration for Ribbon (client-side load balancing). */
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class) // Has a RestTemplate dependency
@ConditionalOnBean(LoadBalancerClient.class) // The bean object that defines LoadBalancerClient
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
// Inject the RestTemplate object with the @loadBalanced flag
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = 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) {
// Use RestTemplateCustomizer to customize the restTemplatecustomizer.customize(restTemplate); }}}); }@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory( LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
/ / create LoadBalancerInterceptor need LoadBalancerClient and LoadBalancerRequestFactory
@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());
// Add the LoadBalancerInterceptor interceptor to restTemplatelist.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; }}}Copy the code
RestTemplate interceptor LoadBalancerInterceptor
LoadBalancerAutoConfiguration automation configuration is mainly to the RestTemplate added a load balancing LoadBalancerInterceptor interceptor. As can be seen from the setInterceptors parameters, the type of the interceptor is ClientHttpRequestInterceptor, if we want to customized RestTemplate can implement this interface to customize, You can then mark the sequence of interceptors with @order.
public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {
if (this.interceptors ! = interceptors) {this.interceptors.clear();
this.interceptors.addAll(interceptors);
// Sort according to the Order of @order annotations
AnnotationAwareOrderComparator.sort(this.interceptors); }}Copy the code
The Interceptors are set in the parent class of The RestTemplate, InterceptingHttpAccessor, whose class structure is shown below.
From the restTemplate. GetForEntity (” http://demo-producer/v1/uuid “, String class) this GET request check, is how to use LoadBalancerInterceptor. As you work your way through, you can see that you’re finally getting into the doExecute method.
In the doExecute method, you first create a ClientHttpRequest based on the URL, method, and then use the ClientHttpRequest to initiate the request.
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
ClientHttpResponse response = null;
try {
// Create a ClientHttpRequest
ClientHttpRequest request = createRequest(url, method);
if(requestCallback ! =null) {
requestCallback.doWithRequest(request);
}
// Call the execute() method of ClientHttpRequest
response = request.execute();
// Process the result
handleResponse(url, method, response);
return(responseExtractor ! =null ? responseExtractor.extractData(response) : null);
}
catch (IOException ex) {
// ...
}
finally {
if(response ! =null) { response.close(); }}}protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
ClientHttpRequest request = getRequestFactory().createRequest(url, method);
initialize(request);
if (logger.isDebugEnabled()) {
logger.debug("HTTP " + method.name() + "" + url);
}
return request;
}
Copy the code
InterceptingHttpAccessor overrides the parent HttpAccessor getRequestFactory method. The parent class is the default requestFactory SimpleClientHttpRequestFactory.
In the rewritten getRequestFactory method, if the interceptor is not empty, Based on the parent class default SimpleClientHttpRequestFactory founded InterceptingClientHttpRequestFactory and interceptors.
public ClientHttpRequestFactory getRequestFactory(a) {
List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
if(! CollectionUtils.isEmpty(interceptors)) { ClientHttpRequestFactory factory =this.interceptingRequestFactory;
if (factory == null) {
/ / incoming SimpleClientHttpRequestFactory and ClientHttpRequestInterceptor interceptors
factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
this.interceptingRequestFactory = factory;
}
return factory;
}
else {
return super.getRequestFactory(); }}Copy the code
That is called the createRequest method to create ClientHttpRequest InterceptingClientHttpRequestFactory. Inside you can see, the actual type is InterceptingClientHttpRequest ClientHttpRequest.
protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) {
return new InterceptingClientHttpRequest(requestFactory, this.interceptors, uri, httpMethod);
}
Copy the code
InterceptingClientHttpRequest class structure is as follows:
In the doExecute RestTemplate call request. The execute () is invoked the InterceptingClientHttpRequest AbstractClientHttpRequest of parent The execute method. Step by step in to can find finally is actually called InterceptingClientHttpRequest executeInternal method.
In InterceptingClientHttpRequest executeInternal method, founded the InterceptingRequestExecution to execute the request. In InterceptingRequestExecution the execute method, first traverse to perform all interceptors, then initiated by ClientHttpRequest real HTTP requests.
protected final ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
/ / create InterceptingRequestExecution
InterceptingRequestExecution requestExecution = new InterceptingRequestExecution();
// Request invocation
return requestExecution.execute(this, bufferedOutput);
}
private class InterceptingRequestExecution implements ClientHttpRequestExecution {
private final Iterator<ClientHttpRequestInterceptor> iterator;
public InterceptingRequestExecution(a) {
// Interceptor iterator
this.iterator = interceptors.iterator();
}
@Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
if (this.iterator.hasNext()) {
ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
/ / by using interceptors to intercept processing and the incoming InterceptingRequestExecution
return nextInterceptor.intercept(request, body, this);
}
else {
// The interceptor starts making the actual HTTP request after traversing
HttpMethod method = request.getMethod();
ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
/ /...
returndelegate.execute(); }}}Copy the code
The LoadBalancerInterceptor intercepts the service name from the original address of the request and calls the execute method of the loadBalancer. LoadBalancerClient.
As you can imagine, loadbalancer.execute is to get a specific instance based on the service name and replace the original address with the IP address of the instance. What loadBalancer is?
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
// Original address: http://demo-producer/v1/uuid
final URI originalUri = request.getURI();
// host is the service name: demo-producer
String serviceName = originalUri.getHost();
return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
Copy the code
Load balancing client LoadBalancerClient
When in the configuration LoadBalancerInterceptor, need two parameters, LoadBalancerClient and LoadBalancerRequestFactory, LoadBalancerRequestFactory already know is how to create. Where is the LoadBalancerClient created? Through IDEA search, it can be found that the RibbonAutoConfiguration under the Spring-Cloud-Netflix-Ribbon module is configured. The actual type of the LoadBalancerClient is RibbonLoadBalancerClient.
The configuration class order is EurekaClientAutoConfiguration, RibbonAutoConfiguration, LoadBalancerAutoConfiguration, The load balancing capability of RestTemplate requires the LoadBalancerInterceptor interceptor, and the LoadBalancerClient is required to create the LoadBalancerInterceptor. To obtain an instance based on the service name, LoadBalancerClient requires an instance library, such as a configuration file or a registry. As you can see from this, the RibbonLoadBalancerClient acquires the instance from the Eureka registry by default.
@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
/ / after EurekaClientAutoConfiguration configuration
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
/ / before LoadBalancerAutoConfiguration configuration
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class })
public class RibbonAutoConfiguration {
@Autowired(required = false)
private List<RibbonClientSpecification> configurations = new ArrayList<>();
@Bean
@ConditionalOnMissingBean
public SpringClientFactory springClientFactory(a) {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient(a) {
return newRibbonLoadBalancerClient(springClientFactory()); }}Copy the code
LoadBalancerClient provides three interfaces:
public interface LoadBalancerClient extends ServiceInstanceChooser {
// Find a Server from LoadBalancer to send the request
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
// Take the Server from the incoming ServiceInstance to send the request
<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
// Refactor the original URI
URI reconstructURI(ServiceInstance instance, URI original);
}
Copy the code
Enter the Execute method of RibbonLoadBalancerClient.
- Obtain the load balancer corresponding to the service based on the service name
ILoadBalancer
. - Then an instance is selected from ILoadBalancer based on certain policies
Server
. - Then encapsulate information such as the server and serviceId to
RibbonServer
Is a ServiceInstance, ServiceInstance. - LoadBalancerRequest is finally called
apply
And pass ServiceInstance to replace the service name in the address with the real IP address.
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
return execute(serviceId, request, null);
}
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
// Obtain a load balancer based on the service name
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// Use the load balancer to obtain the instance Server
Server server = getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
// Encapsulate instance information: The parent class of RibbonServer is ServiceInstance
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
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);
}
try {
// Process the address, replacing the service name with the real IP address
T returnVal = request.apply(serviceInstance);
return returnVal;
} catch (Exception ex) {
// ...
}
return null;
}
Copy the code
LoadBalancerRequest is an anonymous class created in the Intercept of LoadBalancerInterceptor. In its functional interface, Request is wrapped in a layer with the ServiceRequestWrapper decorator.
public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) {
return instance -> {
HttpRequest, ServiceRequestWrapper overrides the getURI method.
HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, this.loadBalancer);
if (this.transformers ! =null) {
for (LoadBalancerRequestTransformer transformer : this.transformers) { serviceRequest = transformer.transformRequest(serviceRequest, instance); }}// Continue to execute interceptor
return execution.execute(serviceRequest, body);
};
}
Copy the code
Equestwrapper (loadBalancer) ¶ ServiceRequestWrapper (loadBalancer) ¶ Replace the service name in the original address with the real IP address and port address of the Server.
@Override
public URI getURI(a) {
/ / refactoring URI
URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
return uri;
}
Copy the code
public URI reconstructURI(ServiceInstance instance, URI original) {
Assert.notNull(instance, "instance can not be null");
/ / service name
String serviceId = instance.getServiceId();
RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
URI uri;
Server server;
if (instance instanceof RibbonServer) {
RibbonServer ribbonServer = (RibbonServer) instance;
server = ribbonServer.getServer();
uri = updateToSecureConnectionIfNeeded(original, ribbonServer);
}
else {
server = new Server(instance.getScheme(), instance.getHost(), instance.getPort());
IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
uri = updateToSecureConnectionIfNeeded(original, clientConfig, serverIntrospector, server);
}
// Reconstruct the address
return context.reconstructURIWithServer(server, uri);
}
Copy the code
ReconstructURIWithServer:
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;
}
if (scheme == null) {
scheme = original.getScheme();
}
if (scheme == null) {
scheme = 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 e) {
throw newRuntimeException(e); }}Copy the code
RestTemplate Load balancing summary
At this point, we’ve pretty much figured out how a simple @loadbalanced annotation can make the RestTemplate LoadBalanced. This section concludes.
1. How does RestTemplate get load balancing capability
- 1) First, RestTemplate is the next network request framework of the Spring-Web module to access third-party RESTful apis
- 2) in the spring cloud microservices architecture, you can make the RestTemplate LoadBalanced by marking it with @loadbalanced
- 3) The core component that makes RestTemplate load balanced is
LoadBalancerAutoConfiguration
Added to it in the configuration classLoadBalancerInterceptor
Load balancing interceptor - 4) RestTemplate customizes by iterating through all interceptors before making an HTTP call. LoadBalancerInterceptor replaces the service name in the URI with the real IP address of the instance. Once the customization is complete, a real HTTP request is made.
- 5) The LoadBalancerInterceptor mainly uses the LoadBalancerClient to reconstruct the URI. The LoadBalancerClient can find an available instance based on the service name and reconstruct the URI.
2. Core components
There are multiple modules involved here. Here is the module to which the core component belongs:
Spring – the web:
- RestTemplate
- InterceptingClientHttpRequest: perform interceptors, and launched the final HTTP calls
Spring – the cloud – Commons:
- @LoadBalanced
- LoadBalancerAutoConfiguration
- LoadBalancerRequestFactory: create decoration class ServiceRequestWrapper replace the original HttpRequest, overloading getURI method.
- LoadBalancerInterceptor: load balancing interceptor
- LoadBalancerClient: load balancing client interface
Spring – the cloud – netflix – ribbon:
- RibbonLoadBalancerClient: Implementation class of LoadBalancerClient, the load balancing client of the Ribbon
- RibbonAutoConfiguration
Ribbon – loadbalancer:
- ILoadBalancer: load balancer
- Server: an instance
3. Finally, use another diagram to clarify the relationship of the RestTemplate block