淺談Spring Cloud Ribbon的原理
Ribbon是Netflix發(fā)布的開(kāi)源項(xiàng)目,主要功能是提供客戶端的軟件負(fù)載均衡算法,將Netflix的中間層服務(wù)連接在一起。Ribbon客戶端組件提供一系列完善的配置項(xiàng)如連接超時(shí),重試等。簡(jiǎn)單的說(shuō),就是在配置文件中列出Load Balancer(簡(jiǎn)稱LB)后面所有的機(jī)器,Ribbon會(huì)自動(dòng)的幫助你基于某種規(guī)則(如簡(jiǎn)單輪詢,隨即連接等)去連接這些機(jī)器。我們也很容易使用Ribbon實(shí)現(xiàn)自定義的負(fù)載均衡算法。
說(shuō)起負(fù)載均衡一般都會(huì)想到服務(wù)端的負(fù)載均衡,常用產(chǎn)品包括LBS硬件或云服務(wù)、Nginx等,都是耳熟能詳?shù)漠a(chǎn)品。
而Spring Cloud提供了讓服務(wù)調(diào)用端具備負(fù)載均衡能力的Ribbon,通過(guò)和Eureka的緊密結(jié)合,不用在服務(wù)集群內(nèi)再架設(shè)負(fù)載均衡服務(wù),很大程度簡(jiǎn)化了服務(wù)集群內(nèi)的架構(gòu)。
具體也不想多寫(xiě)虛的介紹,反正哪里都能看得到相關(guān)的介紹。
直接開(kāi)擼代碼,通過(guò)代碼來(lái)看Ribbon是如何實(shí)現(xiàn)的。
配置
詳解:
1.RibbonAutoConfiguration配置生成RibbonLoadBalancerClient實(shí)例。
代碼位置:
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 { // 略 @Bean @ConditionalOnMissingBean(LoadBalancerClient.class) public LoadBalancerClient loadBalancerClient() { return new RibbonLoadBalancerClient(springClientFactory()); } // 略 }
先看配置條件項(xiàng),RibbonAutoConfiguration配置必須在LoadBalancerAutoConfiguration配置前執(zhí)行,因?yàn)樵贚oadBalancerAutoConfiguration配置中會(huì)使用RibbonLoadBalancerClient實(shí)例。
RibbonLoadBalancerClient繼承自LoadBalancerClient接口,是負(fù)載均衡客戶端,也是負(fù)載均衡策略的調(diào)用方。
2.LoadBalancerInterceptorConfig配置生成:
1).負(fù)載均衡攔截器LoadBalancerInterceptor實(shí)例
包含:
LoadBalancerClient實(shí)現(xiàn)類的RibbonLoadBalancerClient實(shí)例
負(fù)載均衡的請(qǐng)求創(chuàng)建工廠LoadBalancerRequestFactory:實(shí)例
2).RestTemplate自定義的RestTemplateCustomizer實(shí)例
代碼位置:
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 { // 略 @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 new RestTemplateCustomizer() { @Override public void customize(RestTemplate restTemplate) { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); } }; } } // 略 }
先看配置條件項(xiàng):
要求在項(xiàng)目環(huán)境中必須要有RestTemplate類。
要求必須要有LoadBalancerClient接口的實(shí)現(xiàn)類的實(shí)例,也就是上一步生成的RibbonLoadBalancerClient。
3.通過(guò)上面一步創(chuàng)建的RestTemplateCustomizer配置所有RestTemplate實(shí)例,就是將負(fù)載均衡攔截器設(shè)置給RestTemplate實(shí)例。
@Configuration @ConditionalOnClass(RestTemplate.class) @ConditionalOnBean(LoadBalancerClient.class) @EnableConfigurationProperties(LoadBalancerRetryProperties.class) public class LoadBalancerAutoConfiguration { // 略 @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializer( final List<RestTemplateCustomizer> customizers) { return new SmartInitializingSingleton() { @Override public void afterSingletonsInstantiated() { for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) { for (RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); } } } }; } // 略 @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 new RestTemplateCustomizer() { @Override public void customize(RestTemplate restTemplate) { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); } }; } } // 略 }
restTemplate.setInterceptors(list)這個(gè)地方就是注入負(fù)載均衡攔截器的地方LoadBalancerInterceptor。
從這個(gè)地方實(shí)際上也可以猜出來(lái),RestTemplate可以通過(guò)注入的攔截器來(lái)構(gòu)建相應(yīng)的請(qǐng)求實(shí)現(xiàn)負(fù)載均衡。
也能看出來(lái)可以自定義攔截器實(shí)現(xiàn)其他目的。
4.RibbonClientConfiguration配置生成ZoneAwareLoadBalancer實(shí)例
代碼位置:
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 should be the default, first should 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 { // 略 @Bean @ConditionalOnMissingBean public ILoadBalancer ribbonLoadBalancer(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); } // 略 }
ZoneAwareLoadBalancer繼承自ILoadBalancer接口,該接口有一個(gè)方法:
/** * 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就是一個(gè)具體的負(fù)載均衡實(shí)現(xiàn)類,也是默認(rèn)的負(fù)載均衡類,通過(guò)對(duì)chooseServer方法的實(shí)現(xiàn)選取某個(gè)服務(wù)實(shí)例。
攔截&請(qǐng)求
1.使用RestTemplate進(jìn)行Get、Post等各種請(qǐng)求,都是通過(guò)doExecute方法實(shí)現(xiàn)
代碼位置:
spring-web-4.3.12.RELEASE.jar
org.springframework.web.client
RestTemplate.class
public class RestTemplate extends InterceptingHttpAccessor implements RestOperations { // 略 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 { ClientHttpRequest request = createRequest(url, method); if (requestCallback != null) { requestCallback.doWithRequest(request); } response = request.execute(); 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(); } } } // 略 }
支持的各種http請(qǐng)求方法最終都是調(diào)用doExecute方法,該方法內(nèi)調(diào)用創(chuàng)建方法創(chuàng)建請(qǐng)求實(shí)例,并執(zhí)行請(qǐng)求得到響應(yīng)對(duì)象。
2.生成請(qǐng)求實(shí)例創(chuàng)建工廠
上一步代碼中,調(diào)用createRequest方法創(chuàng)建請(qǐng)求實(shí)例,這個(gè)方法是定義在父類中。
先整理出主要的繼承關(guān)系:
createRequest方法實(shí)際是定義在HttpAccessor抽象類中。
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; } }
在createRequest方法中調(diào)用getRequestFactory方法獲得請(qǐng)求實(shí)例創(chuàng)建工廠,實(shí)際上getRequestFactory并不是當(dāng)前HttpAccessor類中定義的,而是在子類InterceptingHttpAccessor中定義的。
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; } } }
在這里做了個(gè)小動(dòng)作,首先還是通過(guò)HttpAccessor類創(chuàng)建并獲得SimpleClientHttpRequestFactory工廠,這個(gè)工廠主要就是在沒(méi)有攔截器的時(shí)候創(chuàng)建基本請(qǐng)求實(shí)例。
其次,在有攔截器注入的情況下,創(chuàng)建InterceptingClientHttpRequestFactory工廠,該工廠就是創(chuàng)建帶攔截器的請(qǐng)求實(shí)例,因?yàn)樽⑷肓素?fù)載均衡攔截器,所以這里就從InterceptingClientHttpRequestFactory工廠創(chuàng)建。
3.通過(guò)工廠創(chuàng)建請(qǐng)求實(shí)例
創(chuàng)建實(shí)例就看工廠的createRequest方法。
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); } }
就是new了個(gè)InterceptingClientHttpRequest實(shí)例,并且把攔截器、基本請(qǐng)求實(shí)例創(chuàng)建工廠注進(jìn)去。
4.請(qǐng)求實(shí)例調(diào)用配置階段注入的負(fù)載均衡攔截器的攔截方法intercept
可從第1步看出,創(chuàng)建完請(qǐng)求實(shí)例后,通過(guò)執(zhí)行請(qǐng)求實(shí)例的execute方法執(zhí)行請(qǐng)求。
ClientHttpRequest request = createRequest(url, method); if (requestCallback != null) { requestCallback.doWithRequest(request); } response = request.execute();
實(shí)際請(qǐng)求實(shí)例是InterceptingClientHttpRequest,execute實(shí)際是在它的父類中。
類定義位置:
spring-web-4.3.12.RELEASE.jar
org.springframework.http.client
InterceptingClientHttpRequest.class
看一下它們的繼承關(guān)系。
在execute方法中實(shí)際調(diào)用了子類實(shí)現(xiàn)的executeInternal方法。
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; }
其實(shí)就是InterceptingClientHttpRequest類的executeInternal方法,其中,又調(diào)用了一個(gè)執(zhí)行器InterceptingRequestExecution的execute,通關(guān)判斷如果有攔截器注入進(jìn)來(lái)過(guò),就調(diào)用攔截器的intercept方法。
這里的攔截器實(shí)際上就是在配置階段注入進(jìn)RestTemplate實(shí)例的負(fù)載均衡攔截器LoadBalancerInterceptor實(shí)例,可參考上面配置階段的第2步。
class InterceptingClientHttpRequest extends AbstractBufferingClientHttpRequest { // 略 @Override protected final ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException { InterceptingRequestExecution requestExecution = new InterceptingRequestExecution(); return requestExecution.execute(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.execute(); } } } }
5.負(fù)載均衡攔截器調(diào)用負(fù)載均衡客戶端
在負(fù)載均衡攔截器LoadBalancerInterceptor類的intercept方法中,又調(diào)用了負(fù)載均衡客戶端LoadBalancerClient實(shí)現(xiàn)類的execute方法。
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)); } }
在配置階段的第1步,可以看到實(shí)現(xiàn)類是RibbonLoadBalancerClient。
6.負(fù)載均衡客戶端調(diào)用負(fù)載均衡策略選取目標(biāo)服務(wù)實(shí)例并發(fā)起請(qǐng)求
在RibbonLoadBalancerClient的第一個(gè)execute方法以及getServer方法中可以看到,實(shí)際上是通過(guò)ILoadBalancer的負(fù)載均衡器實(shí)現(xiàn)類作的chooseServer方法選取一個(gè)服務(wù),交給接下來(lái)的請(qǐng)求對(duì)象發(fā)起一個(gè)請(qǐng)求。
這里的負(fù)載均衡實(shí)現(xiàn)類默認(rèn)是ZoneAwareLoadBalancer區(qū)域感知負(fù)載均衡器實(shí)例,其內(nèi)部通過(guò)均衡策略選擇一個(gè)服務(wù)。
ZoneAwareLoadBalancer的創(chuàng)建可以參考配置階段的第4步。
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; } // 略 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; } // 略 } }
代碼擼完,總結(jié)下。
普通使用RestTemplate請(qǐng)求其他服務(wù)時(shí),內(nèi)部使用的就是常規(guī)的http請(qǐng)求實(shí)例發(fā)送請(qǐng)求。
為RestTemplate增加了@LoanBalanced 注解后,實(shí)際上通過(guò)配置,為RestTemplate注入負(fù)載均衡攔截器,讓負(fù)載均衡器選擇根據(jù)其對(duì)應(yīng)的策略選擇合適的服務(wù)后,再發(fā)送請(qǐng)求。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java中構(gòu)造器內(nèi)部的多態(tài)方法的行為實(shí)例分析
這篇文章主要介紹了Java中構(gòu)造器內(nèi)部的多態(tài)方法的行為,結(jié)合實(shí)例形式分析了java構(gòu)造器內(nèi)部多態(tài)方法相關(guān)原理、功能及操作技巧,需要的朋友可以參考下2019-10-10java中為何重寫(xiě)equals時(shí)必須重寫(xiě)hashCode方法詳解
這篇文章主要給大家介紹了關(guān)于java中為什么重寫(xiě)equals時(shí)必須重寫(xiě)hashCode方法的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11java實(shí)現(xiàn)百度坐標(biāo)的摩卡托坐標(biāo)與火星坐標(biāo)轉(zhuǎn)換的示例
這篇文章主要介紹了java實(shí)現(xiàn)百度坐標(biāo)的摩卡托坐標(biāo)與火星坐標(biāo)轉(zhuǎn)換的示例,需要的朋友可以參考下2014-03-03Spring中ApplicationEventPublisher發(fā)布訂閱模式的實(shí)現(xiàn)
本文主要介紹了Spring中ApplicationEventPublisher發(fā)布訂閱模式的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07