Make writing a habit together! This is the 11th day of my participation in the “Gold Digging Day New Plan · April More text Challenge”. Click here for more details

A Preliminary study on Ribbon Principle

Determination is the beginning of success

Using the Ribbon

The Ribbon uses a ** @loadBalanced ** annotation on the RestTemplate method to load balance requests

@Configuration
public class RestConfig {
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate(a){
        return newRestTemplate(); }}Copy the code

Load balancing principle diagram

The RestTemplate method

If you don’t use the Ribbon, use a new Ribbon when making HTTP requestsRestTemplateObject, which can be called with a corresponding method such as getForEntity(). In this case, you can request it directly. There is no load balancing

@Override
public <T> ResponseEntity<T> getForEntity(String url, Class
       
         responseType, Object... uriVariables)
       
        throws RestClientException {
    RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
    ResponseExtractor<ResponseEntity<T>> responseExtractor = 
        responseEntityExtractor(responseType);
    // Execute the request
    return nonNull(execute(url, HttpMethod.GET, requestCallback, 
                           responseExtractor, uriVariables));
}
Copy the code

LoadBalanced annotation source code

/** * Annotation to mark a RestTemplate 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

This annotation is used on the RestTemplate class to configure LoadBalancerClient, Let’s look for LoadBalancerClient this interface and see in which package package org. Springframework. Cloud. Client. The loadbalancer;

public interface LoadBalancerClient extends ServiceInstanceChooser {
    // Executes the service request specified by the load balancer
	<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
    // Executes the service request specified by the load balancer
	<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest
       
         request)
        throws IOException;
    // Create an appropriate URI with a real host and port for the system to use.
    // Some systems use urIs with logical service names as hosts,
    / / such as http://myservice/path/to/service
    // This will replace the service name with host: port in ServiceInstan
	URI reconstructURI(ServiceInstance instance, URI original);
}
/** * Select a service instance */ through the load balancer
public interface ServiceInstanceChooser {
	ServiceInstance choose(String serviceId);
}
Copy the code

Ribbon RibbonClientConfiguration client configuration class

public class RibbonClientConfiguration {
// Client connection timed out
public static final int DEFAULT_CONNECT_TIMEOUT = 1000;

// Client read time out
public static final int DEFAULT_READ_TIMEOUT = 1000;

@RibbonClientName
private String name = "client";

@Autowired
privatePropertiesFactory propertiesFactory; . }Copy the code

1. RibbonClientConfig Client configuration

// Ribbon client configuration class
@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig(a) {
    DefaultClientConfigImpl config = new DefaultClientConfigImpl();
    config.loadProperties(this.name);
    config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
    config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
    config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
    return config;
}
Copy the code

2. RibbonRule rules

// Ribbon rules class
@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
    if (this.propertiesFactory.isSet(IRule.class, name)) {
        return this.propertiesFactory.get(IRule.class, config, name);
    }
    ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
    rule.initWithNiwsConfig(config);
    return rule;
}
Copy the code

3. RibbonPing

/ / Ribbon Ping class
@Bean
@ConditionalOnMissingBean
public IPing ribbonPing(IClientConfig config) {
    if (this.propertiesFactory.isSet(IPing.class, name)) {
        return this.propertiesFactory.get(IPing.class, config, name);
    }
    return new DummyPing();
}

Copy the code

4. Ribbon ribbonServerList Service list

// Ribbon Service list
@Bean
@ConditionalOnMissingBean
@SuppressWarnings("unchecked")
public ServerList<Server> ribbonServerList(IClientConfig config) {
    if (this.propertiesFactory.isSet(ServerList.class, name)) {
        return this.propertiesFactory.get(ServerList.class, config, name);
    }
    ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
    serverList.initWithNiwsConfig(config);
    return serverList;
}
Copy the code

5. RibbonServerListUpdater Ribbon Service list update

// Ribbon service list updater. the default value is PollingServerListUpdater
@Bean
@ConditionalOnMissingBean
public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
    return new PollingServerListUpdater(config);
}

Copy the code

6. RibbonLoadBalancer Load balancer

// Ribbon service load balancer
@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

7. RibbonServerListFilter Service list filter

// Service list filter
@Bean
@ConditionalOnMissingBean
@SuppressWarnings("unchecked")
public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) {
    if (this.propertiesFactory.isSet(ServerListFilter.class, name)) {
        return this.propertiesFactory.get(ServerListFilter.class, config, name);
    }
    ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
    filter.initWithNiwsConfig(config);
    return filter;
}

Copy the code

Load balancer initializes the core configuration class

Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

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

@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
// Load balancing RestTemplate performs initial configuration
@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); }}}); }@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory( LoadBalancerClient loadBalancerClient) {
    return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
}
// Load balancing interceptor configuration
@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

Load balancing interceptor LoadBalancerInterceptor

@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
        final ClientHttpRequestExecution execution) throws IOException {
    // Resolve the service address
    final URI originalUri = request.getURI();
    // Request the service name
    String serviceName = originalUri.getHost();
    // Request flow for execution
    return this.loadBalancer.execute(serviceName,
            this.requestFactory.createRequest(request, body, execution));
}
Copy the code

The loadBalancer executes the request loadbalancer.execute

public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
        throws IOException {
    // Obtain the load balancer
    ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
    // Select available services through the load balancer
    Server server = getServer(loadBalancer, hint);
    if (server == null) {
        throw new IllegalStateException("No instances available for " + serviceId);
    }
    // Create Ribbon service
    RibbonServer ribbonServer = new RibbonServer(serviceId, server,
            isSecure(server, serviceId),
            serverIntrospector(serviceId).getMetadata(server));
    // Actually execute the request
    return execute(serviceId, ribbonServer, request);
}
Copy the code

Actually execute the request

Execution is performed using a LoadBalancerRequest 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 the service is empty then an exception is thrown with no instance available
    if (server == null) {
        throw new IllegalStateException("No instances available for " + serviceId);
    }

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

    try {
        // The real request method
        T returnVal = request.apply(serviceInstance);
        statsRecorder.recordStats(returnVal);
        return returnVal;
    }
    return null;
}

Copy the code

ServiceRequestWrapper wrapper request for a service request

@Override
public ListenableFuture<ClientHttpResponse> intercept(final HttpRequest request,
        final byte[] body, final AsyncClientHttpRequestExecution execution)
        throws IOException {
    // Get the request path URI
    final URI originalUri = request.getURI();
    // Get the address of the service
    String serviceName = originalUri.getHost();
    return this.loadBalancer.execute(serviceName,
            new LoadBalancerRequest<ListenableFuture<ClientHttpResponse>>() {
                // The method of execution
                @Override
                public ListenableFuture<ClientHttpResponse> apply(
                        final ServiceInstance instance) throws Exception {
                    // Encapsulate the request
                    HttpRequest serviceRequest = new ServiceRequestWrapper(request,
                            instance, AsyncLoadBalancerInterceptor.this.loadBalancer);
                    // Perform the service call access
                    returnexecution.executeAsync(serviceRequest, body); }}); }Copy the code

summary

The RestTemplate request is encapsulated by an interceptor, and the request address is resolved to a corresponding available service address, and the request service instance is selected by a load balancer