Talking about the principle of Spring Cloud Ribbon, cloudribbon
Ribbon is an open-source Project released by Netflix. It provides client-side software Load Balancing algorithms to connect Netflix's intermediate layer services. The Ribbon client component provides a series of complete configuration items, such as connection timeout and retry. Simply put, the configuration file lists all the machines after Load Balancer (LB). Ribbon will automatically help you based on certain rules (such as simple round robin and instant connection) connect these machines. We can also easily use Ribbon to implement custom load balancing algorithms.
Generally speaking, Server Load balancer is designed for Server Load balancer. Common products such as LBS hardware, cloud services, and Nginx are all well-known products.
Spring Cloud provides a Ribbon that enables the service caller to have load balancing capabilities. By working closely with Eureka, you do not need to set up Server Load balancer services in the service cluster, greatly simplifies the architecture in the service cluster.
I do not want to write more virtual descriptions. I can see relevant introductions wherever I am.
Open the keystore code to see how Ribbon is implemented.
Configuration
Details:
1. Configure RibbonAutoConfiguration to generate a RibbonLoadBalancerClient instance.
Code Location:
Spring-cloud-netflix-core-1.3.5.RELEASE.jar
Org. springframework. cloud. netflix. ribbon
RibbonAutoConfiguration. class
@ Configuration @ ConditionalOnClass ({IClient. class, RestTemplate. class, AsyncRestTemplate. class, Ribbon. class}) @ RibbonClients @ AutoConfigureAfter (name = "org. springframework. cloud. netflix. eureka. eurekaClientAutoConfiguration ") @ AutoConfigureBefore ({LoadBalancerAutoConfiguration. class, AsyncLoadBalancerAutoConfiguration. class}) @ EnableConfigurationProperties (RibbonEagerLoadProperties. class) public class RibbonAutoConfiguration {// slightly @ Bean @ ConditionalOnMissingBean (LoadBalancerClient. class) public LoadBalancerClient loadBalancerClient () {return new RibbonLoadBalancerClient (springClientFactory ();} // omitted}
First, check the configuration conditions. The RibbonAutoConfiguration configuration must be executed before the LoadBalancerAutoConfiguration configuration, because the RibbonLoadBalancerClient instance is used in the LoadBalancerAutoConfiguration configuration.
RibbonLoadBalancerClient inherits from the LoadBalancerClient interface. It is a server Load balancer client and a caller of the Server Load balancer policy.
2. LoadBalancerInterceptorConfig configuration generation:
1). server load balancerinterceptor instance
Includes:
RibbonLoadBalancerClient instance of the LoadBalancerClient implementation class
Server Load balancer request creation factory LoadBalancerRequestFactory: instance
2) RestTemplateCustomizer instance customized by RestTemplate
Code Location:
Spring-cloud-commons-1.2.4.RELEASE.jar
Org. springframework. cloud. client. loadbalancer
LoadBalancerAutoConfiguration. class
@ Configuration @ ConditionalOnClass (RestTemplate. class) @ ConditionalOnBean (LoadBalancerClient. class) @ EnableConfigurationProperties (LoadBalancerRetryProperties. class) public class LoadBalancerAutoConfiguration {// slightly @ Bean @ brief public LoadBalancerRequestFactory loadBalancerRequestFactory (LoadBalancerClient loadBalancerClient) {return new response (loadBalancerClient, transformers);} @ Configuration @ submit. springframework. retry. support. retryTemplate ") static class extends {@ Bean public extends ribbonInterceptor (LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {return new LoadBalancerInterceptor (loadBalancerClient, requestFactory );} @ Bean @ ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer (final LoadBalancerInterceptor loadBalancerInterceptor) {return new listener () {@ Override public void customize (RestTemplate restTemplate) {List <ClientHttpRequestInterceptor> list = new ArrayList <> (restTemplate. getInterceptors (); list. add (loadBalancerInterceptor); restTemplate. setInterceptors (list) ;}}}// omitted}
First, configure the condition items:
The RestTemplate class must be included in the project environment.
You must have an instance of the implementation class of the LoadBalancerClient interface, that is, the RibbonLoadBalancerClient generated in the previous step.
3. configure all RestTemplate instances through the RestTemplateCustomizer created in the previous step, that is, set the Server Load balancer Interceptor to the RestTemplate instance.
@ Configuration @ ConditionalOnClass (RestTemplate. class) @ ConditionalOnBean (LoadBalancerClient. class) @ EnableConfigurationProperties (LoadBalancerRetryProperties. class) public class LoadBalancerAutoConfiguration {// slightly @ Bean public SmartInitializingSingleton values (final List <RestTemplateCustomizer> mizmizers) {return new SmartInitializingSingleton () {@ Override public void Merge () {for (RestTemplate restTemplate: LoadBalancerAutoConfiguration. this. restTemplates) {for (RestTemplateCustomizer customizer: mizmizers) {customizer. customize (restTemplate) ;}}};/// slightly @ Configuration @ ConditionalOnMissingClass ("org. springframework. retry. support. retryTemplate ") static class extends {@ Bean public extends ribbonInterceptor (LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {return new LoadBalancerInterceptor (loadBalancerClient, requestFactory );} @ Bean @ ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer (final LoadBalancerInterceptor loadBalancerInterceptor) {return new listener () {@ Override public void customize (RestTemplate restTemplate) {List <ClientHttpRequestInterceptor> list = new ArrayList <> (restTemplate. getInterceptors (); list. add (loadBalancerInterceptor); restTemplate. setInterceptors (list) ;}}}// omitted}
RestTemplate. setInterceptors (list) is the LoadBalancerInterceptor injected into the Server Load balancer interceptor.
From this point, we can also guess that RestTemplate can build corresponding requests through the injection Interceptor to achieve load balancing.
It can also be seen that the interceptor can be customized for other purposes.
4. Configure RibbonClientConfiguration to generate a ZoneAwareLoadBalancer instance
Code Location:
Spring-cloud-netflix-core-1.3.5.RELEASE.jar
Org. springframework. cloud. netflix. ribbon
RibbonClientConfiguration. class
@ SuppressWarnings ("deprecation") @ Configuration @ EnableConfigurationProperties // Order is important here, last shocould be the default, first shocould be optional // see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653@Import ({OkHttpRibbonConfiguration. class, RestClientRibbonConfiguration. class, HttpClientRibbonConfiguration. class}) public class RibbonClientConfiguration {// slightly @ Bean @ ConditionalOnMissingBean public ILoadBalancer extends (IClientConfig config, ServerList <Server> serverList, ServerListFilter <Server> 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);} // omitted}
ZoneAwareLoadBalancer inherits from the ILoadBalancer interface, which has a method:
/** * Choose a server from load balancer. * * @param key An object that the load balancer may use to determine which server to return. null if * the load balancer does not use this parameter. * @return server chosen */ public Server chooseServer(Object key);
ZoneAwareLoadBalancer is a specific load balancing implementation class and a default Load Balancing class. It selects a service instance by implementing the chooseServer method.
Interception & request
1. Using RestTemplate for Get, Post, and other requests is implemented through the doExecute method.
Code Location:
Spring-web-4.3.12.RELEASE.jar
Org. springframework. web. client
RestTemplate. class
Public class RestTemplate extends implements RestOperations {// slightly protected <T> T doExecute (URI url, HttpMethod method, RequestCallback requestCallback, ResponseExtractor <T> responseExtractor) throws RestClientException {Assert. notNull (url, "'url' must not be null"); Assert. notNull (method, "'method' must not be null"); ClientHttpResponse response = null; try {ClientHttpR Equest request = createRequest (url, method); if (requestCallback! = Null) {requestCallback. doWithRequest (request);} response = request.exe cute (); handleResponse (url, method, response); if (responseExtractor! = Null) {return responseExtractor. extractData (response) ;}else {return null ;}} catch (IOException ex) {String resource = url. toString (); String query = url. getRawQuery (); resource = (query! = Null? Resource. substring (0, resource. indexOf ('? '): Resource); throw new ResourceAccessException ("I/O error on" + method. name () + "request for \" "+ resource +" \ ":" + ex. getMessage (), ex);} finally {if (response! = Null) {response. close () ;}}// omitted}
All the supported http request methods ultimately call the doExecute method. In this method, call the creation method to create a request instance and execute the request to obtain the response object.
2. Generate a request instance to create a factory
In the previous Code, call the createRequest method to create a request instance, which is defined in the parent class.
First, sort out the main inheritance relationships:
The createRequest method is actually defined in the HttpAccessor abstract class.
public abstract class HttpAccessor { private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); public void setRequestFactory(ClientHttpRequestFactory requestFactory) { Assert.notNull(requestFactory, "ClientHttpRequestFactory must not be null"); this.requestFactory = requestFactory; } public ClientHttpRequestFactory getRequestFactory() { return this.requestFactory; } protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException { ClientHttpRequest request = getRequestFactory().createRequest(url, method); if (logger.isDebugEnabled()) { logger.debug("Created " + method.name() + " request for \"" + url + "\""); } return request; }}
Call the getRequestFactory method in the createRequest method to obtain the request instance creation factory. In fact, getRequestFactory is not defined in the current HttpAccessor class, but defined in the InterceptingHttpAccessor subclass.
public abstract class InterceptingHttpAccessor extends HttpAccessor { private List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>(); public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) { this.interceptors = interceptors; } public List<ClientHttpRequestInterceptor> getInterceptors() { return interceptors; } @Override public ClientHttpRequestFactory getRequestFactory() { ClientHttpRequestFactory delegate = super.getRequestFactory(); if (!CollectionUtils.isEmpty(getInterceptors())) { return new InterceptingClientHttpRequestFactory(delegate, getInterceptors()); } else { return delegate; } }}
Here, I made a small step. First, I used the HttpAccessor class to create and obtain the SimpleClientHttpRequestFactory factory. This factory is mainly used to create basic request instances when no interceptor is available.
Secondly, when an interceptor is injected, the InterceptingClientHttpRequestFactory factory is created to create a request instance with an interceptor. Because the Server Load balancer interceptor is injected, it is created from the InterceptingClientHttpRequestFactory factory.
3. Create a request instance through the factory
The createRequest method of the factory is used to create an instance.
public class InterceptingClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper { private final List<ClientHttpRequestInterceptor> interceptors; public InterceptingClientHttpRequestFactory(ClientHttpRequestFactory requestFactory, List<ClientHttpRequestInterceptor> interceptors) { super(requestFactory); this.interceptors = (interceptors != null ? interceptors : Collections.<ClientHttpRequestInterceptor>emptyList()); } @Override protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) { return new InterceptingClientHttpRequest(requestFactory, this.interceptors, uri, httpMethod); }}
An InterceptingClientHttpRequest instance is created, and the interceptor and basic request instance creation factory are added.
4. Request the instance to call the intercept method of the Server Load balancer interceptor injected in the configuration phase.
You can see from step 1 that after the request instance is created, the request is executed using the execute method of the request instance.
ClientHttpRequest request = createRequest(url, method);if (requestCallback != null) { requestCallback.doWithRequest(request);}response = request.execute();
The actual request instance is InterceptingClientHttpRequest, And the execute is actually in its parent class.
Class Definition location:
Spring-web-4.3.12.RELEASE.jar
Org. springframework. http. client
InterceptingClientHttpRequest. class
Let's take a look at their inheritance relationships.
In the execute method, the executeInternal method implemented by the subclass is actually called.
public abstract class AbstractClientHttpRequest implements ClientHttpRequest { private final HttpHeaders headers = new HttpHeaders(); private boolean executed = false; @Override public final HttpHeaders getHeaders() { return (this.executed ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers); } @Override public final OutputStream getBody() throws IOException { assertNotExecuted(); return getBodyInternal(this.headers); } @Override public final ClientHttpResponse execute() throws IOException { assertNotExecuted(); ClientHttpResponse result = executeInternal(this.headers); this.executed = true; return result; } protected void assertNotExecuted() { Assert.state(!this.executed, "ClientHttpRequest already executed"); } protected abstract OutputStream getBodyInternal(HttpHeaders headers) throws IOException; protected abstract ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException;}
It is actually the executeInternal method of the InterceptingClientHttpRequest class. In this method, the execute of InterceptingRequestExecution is called. If the interceptor is injected, the intercept method of the interceptor is called.
The interceptor here is actually the load balancing interceptor instance injected into the RestTemplate instance in the configuration phase. For details, refer to step 1 of the above configuration phase.
Class extends {// slightly @ Override protected final extends executeInternal (HttpHeaders headers, byte [] bufferedOutput) throws IOException {extends requestExecution = new executions (); return requestExecution.exe cute (this, bufferedOutput);} private class InterceptingRequestExecution implements ClientHttpRequestExecution {private final Iterator <ClientHttpRequestInterceptor> iterator; public InterceptingRequestExecution () {this. iterator = interceptors. iterator () ;}@ Override public ClientHttpResponse execute (HttpRequest request, byte [] body) throws IOException {if (this. iterator. hasNext () {ClientHttpRequestInterceptor nextInterceptor = this. iterator. next (); 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.exe cute ();}}}}
5. The Server Load balancer interceptor calls the Server Load balancer client.
In the intercept method of the LoadBalancerInterceptor class of the Server Load balancer interceptor, The execute method of the LoadBalancerClient implementation class is also called.
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 { final URI 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)); }}
In step 1 of the configuration phase, we can see that the implementation class is RibbonLoadBalancerClient.
6. The Server Load balancer client calls the Server Load balancer policy to select the target service instance and initiate a request.
In the first execute method of the RibbonLoadBalancerClient and the getServer method, we can see that the chooseServer method of the ILoadBalancer class is used to select a service and send it to the next request object to initiate a request.
The default implementation class of Server Load balancer is ZoneAwareLoadBalancer, which perceives the Server Load balancer instance and selects a service through the Internal Server Load balancer policy.
For details about how to create ZoneAwareLoadBalancer, refer to step 1 of the configuration phase.
Public class RibbonLoadBalancerClient implements LoadBalancerClient {@ Override public <T> T execute (String serviceId, LoadBalancerRequest <T> request) throws IOException {ILoadBalancer loadBalancer = getLoadBalancer (serviceId ); 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); 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;} // slightly protected Server getServer (ILoadBalancer loadBalancer) {if (loadBalancer = null) {return null;} return loadBalancer. chooseServer ("default"); // TODO: better handling of key} protected ILoadBalancer getLoadBalancer (String serviceId) {return this. clientFactory. getLoadBalancer (serviceId);} public static class RibbonServer implements ServiceInstance {private final String serviceId; private final Server server; private final boolean secure; private Map <String, String> metadata; public RibbonServer (String serviceId, Server server) {this (serviceId, server, false, Collections. <String, String> emptyMap ();} public RibbonServer (String serviceId, Server server, boolean secure, Map <String, String> metadata) {this. serviceId = serviceId; this. server = server; this. secure = secure; this. metadata = metadata;} // omitted }}
After the code is completed, summarize it.
When RestTemplate is used to request other services, the common http request instance is used internally to send requests.
After the @ LoanBalanced annotation is added to the RestTemplate, the Server Load balancer interceptor is injected into the RestTemplate through configuration, so that the Server Load balancer selects the appropriate service based on its corresponding policies and then sends the request.
The above is all the content of this article. I hope it will be helpful for your learning and support for helping customers.