1, Introduction
Ribbon is a component for load balancing on the client side. It is used for load balancing between services in spring cloud microservices. The default is polling algorithm. You can configure other algorithms and customize load balancing algorithms.
Client load balancing: a request has declared which service to call at the client, and then calls one of the multiple node services through a specific load balancing algorithm.
Load balancing on the server side: a request first passes through the proxy server, such as nginx, and then the proxy server reversely proxies the server side through the load balancing algorithm to complete the service call.
2, Automatic assembly
If you want to use ribbon load balancing, you can usually annotate @ LoadBalanced directly, and then use restTemplate to call, which will automatically trigger the load balancing algorithm. Of course, if we use components such as feign and have automatically integrated ribbon, we don't need to do so.
@LoadBalanced @Bean public RestTemplate restTemplate(){ return new RestTemplate(); }
@Dbalanceload annotation
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Qualifier public @interface LoadBalanced { }
An empty annotation has no practical significance.
Spring SPI mechanism, configuration file spring Factories, you can find the automatic assembly class loadbalancenautoconfiguration
@Configuration(proxyBeanMethods = false) // The current environment requires RestTemplate.class @ConditionalOnClass(RestTemplate.class) // The current environment is required to have LoadBalancerClient Implementation class of interface @ConditionalOnBean(LoadBalancerClient.class) // Initialize profile LoadBalancerRetryProperties @EnableConfigurationProperties(LoadBalancerRetryProperties.class) public class LoadBalancerAutoConfiguration { @LoadBalanced @Autowired(required = false) // 1,@AutoWired Collection classes are also automatically loaded list,Will be appropriate RestTemplate Add to restTemplates in // And as for what to load RestTemplate,That's the label@LoadBalanced of RestTemplate // Above we see@LoadBalanced There is one@Qualifier Is the meaning of special labels, so ordinary ones are not added@LoadBalanced // Will not be added to restTemplates Medium private List<RestTemplate> restTemplates = Collections.emptyList(); @Autowired(required = false) private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList(); @Bean // 2,SmartInitializingSingleton The implementation class of the interface will be called after the project is initialized afterSingletonsInstantiated method 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 //Same type bean Only one can be registered @ConditionalOnMissingBean // 3,LoadBalancerRequestFactory Created public LoadBalancerRequestFactory loadBalancerRequestFactory( LoadBalancerClient loadBalancerClient) { return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers); } @Configuration(proxyBeanMethods = false) @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class LoadBalancerInterceptorConfig { // 4,take LoadBalancerClient Interface is created in the implementation class and method 3 LoadBalancerRequestFactory // Inject into the method and become LoadBalancerInterceptor Interceptor parameters @Bean public LoadBalancerInterceptor ribbonInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) { return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); } @Bean @ConditionalOnMissingBean // 5,Created in method 4 LoadBalancerInterceptor Will be injected as method parameters public RestTemplateCustomizer restTemplateCustomizer( final LoadBalancerInterceptor loadBalancerInterceptor) { // customize Method will be replaced by method 2 afterSingletonsInstantiated()Traversal call return restTemplate -> { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; } } /** * Configure retry mechanism */ @Configuration(proxyBeanMethods = false) @ConditionalOnClass(RetryTemplate.class) public static class RetryAutoConfiguration { @Bean @ConditionalOnMissingBean public LoadBalancedRetryFactory loadBalancedRetryFactory() { return new LoadBalancedRetryFactory() { }; } } /** * Auto configuration for retry intercepting mechanism. */ @Configuration(proxyBeanMethods = false) @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); }; } } }
3, Request processing
After the project is started, an interceptor will be added when creating the resttemplate. When the resttemplate is used to send a request, the load balancing policy will be triggered through the interceptor. Take getForObject method call as an example.
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); HttpMessageConverterExtractor<T> responseExtractor = new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables); } // execute public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback, ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException { URI expanded = getUriTemplateHandler().expand(url, uriVariables); return doExecute(expanded, method, requestCallback, responseExtractor); } //doExecute protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback, ResponseExtractor<T> responseExtractor) throws RestClientException { ... ClientHttpResponse response = null; try { // 1.Create a request request ClientHttpRequest request = createRequest(url, method); if (requestCallback != null) { requestCallback.doWithRequest(request); } // 2.Execute the request response = request.execute(); // 3.Encapsulate the response results handleResponse(url, method, response); if (responseExtractor != null) { return responseExtractor.extractData(response); } else { return null; } } ... }
1. createRequest(url, method) creates a request
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException { // The point is here ClientHttpRequest request = getRequestFactory().createRequest(url, method); if (logger.isDebugEnabled()) { logger.debug("Created " + method.name() + " request for \"" + url + "\""); } return request; } //getRequestFactory public ClientHttpRequestFactory getRequestFactory() { ClientHttpRequestFactory delegate = super.getRequestFactory(); // RestTemplate Interceptor is not empty if (!CollectionUtils.isEmpty(getInterceptors())) { return new InterceptingClientHttpRequestFactory(delegate, getInterceptors()); } else { return delegate; } } // InterceptingClientHttpRequestFactory.createRequest(url, method) protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) { // That's what you end up returning request return new InterceptingClientHttpRequest(requestFactory, this.interceptors, uri, httpMethod); }
2. Execute the request execute()
//Current request by InterceptingClientHttpRequest public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException { // 1.iterator Interceptor set // private final Iterator<ClientHttpRequestInterceptor> iterator; if (this.iterator.hasNext()) { ClientHttpRequestInterceptor nextInterceptor = this.iterator.next(); // 2.Interceptor by interceptor, let's look at the most important LoadBalancerInterceptor return nextInterceptor.intercept(request, body, this); } else { ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), request.getMethod()); for (Map.Entry<String, List<String>> entry : request.getHeaders().entrySet()) { List<String> values = entry.getValue(); for (String value : values) { delegate.getHeaders().add(entry.getKey(), value); } } if (body.length > 0) { StreamUtils.copy(body, delegate.getBody()); } return delegate.execute(); } }
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException { final URI originalUri = request.getURI(); String serviceName = originalUri.getHost(); Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri); // Real execution method // private LoadBalancerClient loadBalancer; // LoadBalancerClient The default implementation class is RibbonLoadBalancerClient return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution)); } // RibbonLoadBalancerClient.execute() public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException { // 1.According to the user's request serviceId To get specific information LoadBalanced ILoadBalancer loadBalancer = getLoadBalancer(serviceId); // 2.Get specific server(That is, the specific service information of which port number of which server to locate) Server server = getServer(loadBalancer); if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,serviceId), serverIntrospector(serviceId).getMetadata(server)); // 3.implement HTTP request return execute(serviceId, ribbonServer, request); }
Through the analysis of this method, we can see that a series of algorithms are used to obtain the specific host and port of the service according to the serviceId (i.e. service name) entered by the user, then re encapsulate the HTTP request, and finally execute the HTTP request.
2.1. getLoadBalancer() gets the load balancer
protected ILoadBalancer getLoadBalancer(String serviceId) { return this.clientFactory.getLoadBalancer(serviceId); } // SpringClientFactory.getLoadBalancer() public ILoadBalancer getLoadBalancer(String name) { return getInstance(name, ILoadBalancer.class); } // SpringClientFactory.getInstance() public <C> C getInstance(String name, Class<C> type) { C instance = super.getInstance(name, type); if (instance != null) { return instance; } IClientConfig config = getInstance(name, IClientConfig.class); return instantiateWithConfig(getContext(name), type, config); } // NamedContextFactory.getInstance() public <T> T getInstance(String name, Class<T> type) { AnnotationConfigApplicationContext context = getContext(name); if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, type).length > 0) { // This is the main sentence. Get from the container ILoadBalancer The default implementation is // ZoneAwareLoadBalancer return context.getBean(type); } return null; }
2.2,getServer(loadBalancer)
protected Server getServer(ILoadBalancer loadBalancer) { if (loadBalancer == null) { return null; } // The specific implementation is ZoneAwareLoadBalancer.chooseServer() return loadBalancer.chooseServer("default"); // TODO: better handling of key } // ZoneAwareLoadBalancer.chooseServer() public Server chooseServer(Object key) { // 1.Because the author tested server,Available zone It's one, so I'll go straight super.chooseServer() if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) { logger.debug("Zone aware logic disabled or there is only one zone"); return super.chooseServer(key); } //If more region,The following method will be followed and commented out temporarily ... } //BaseLoadBalancer.chooseServer() public Server chooseServer(Object key) { if (counter == null) { counter = createCounter(); } counter.increment(); if (rule == null) { return null; } else { try { // rule by ZoneAvoidanceRule return rule.choose(key); } catch (Exception e) { logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e); return null; } } } //PredicateBasedRule.choose(key) public Server choose(Object key) { ILoadBalancer lb = getLoadBalancer(); // The point is here, from all server According to the corresponding rule To get a specific server Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key); if (server.isPresent()) { return server.get(); } else { return null; } }
The main function of getServer() is to select a specific Server according to specific rule s. The important implementation is actually in this method
2.3,RibbonLoadBalancerClient.execute(serviceId, ribbonServer, request) executes the HTTP request
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException { ... RibbonLoadBalancerContext context = this.clientFactory .getLoadBalancerContext(serviceId); RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server); try { // The core method here is a callback method, // Specifically, callback LoadBalancerRequestFactory.createRequest()Medium apply()method T returnVal = request.apply(serviceInstance); statsRecorder.recordStats(returnVal); return returnVal; } ... } // Callback method public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) { return new LoadBalancerRequest<ClientHttpResponse>() { @Override // The callback method is here public ClientHttpResponse apply(final ServiceInstance instance) throws Exception { HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer); if (transformers != null) { for (LoadBalancerRequestTransformer transformer : transformers) { serviceRequest = transformer.transformRequest(serviceRequest, instance); } } // Method to be implemented return execution.execute(serviceRequest, body); } }; } //InterceptingRequestExecution.execute() public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException { if (this.iterator.hasNext()) { ClientHttpRequestInterceptor nextInterceptor = this.iterator.next(); return nextInterceptor.intercept(request, body, this); } // Note: there is no iterator,Direct execution request request else { // 1.according to URI Get the request and encapsulate the header ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), request.getMethod()); for (Map.Entry<String, List<String>> entry : request.getHeaders().entrySet()) { List<String> values = entry.getValue(); for (String value : values) { delegate.getHeaders().add(entry.getKey(), value); } } if (body.length > 0) { StreamUtils.copy(body, delegate.getBody()); } // 2.The essence is right HttpURLConnection Implementation of return delegate.execute(); } } }
3. Encapsulation response
protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { //Gets the processor that handled the error ResponseErrorHandler errorHandler = getErrorHandler(); boolean hasError = errorHandler.hasError(response); if (logger.isDebugEnabled()) { try { int code = response.getRawStatusCode(); //Parse response status HttpStatus status = HttpStatus.resolve(code); logger.debug("Response " + (status != null ? status : code)); } catch (IOException ex) { } } if (hasError) { //Handling the corresponding errors errorHandler.handleError(url, method, response); } }
Summary:
1. Create RestTemplate
2. After adding the ribbon dependency, the LoadBalancerInterceptor interceptor will be automatically added to the RestTemplate when the project starts
3. When the user initiates a request according to the RestTemplate, the request will be forwarded to the loadbalancerinceptor for execution. The interceptor will obtain the application server IP and port corresponding to the request according to the specified load balancing method
4. Re encapsulate the request according to the obtained IP and port, send the HTTP request and return the specific response
4, Custom load balancer
Implement the interface IRule, and then take a look at some default rules implemented by ribbon, such as RandomRule. Refer to his writing method to implement the load balancing strategy you want, and finally inject it into the Spring container.
reference resources: https://blog.csdn.net/qq_26323323/article/details/81327669