您好,登錄后才能下訂單哦!
Ribbon是Netflix發布的開源項目,主要功能是提供客戶端的軟件負載均衡算法,將Netflix的中間層服務連接在一起。Ribbon客戶端組件提供一系列完善的配置項如連接超時,重試等。簡單的說,就是在配置文件中列出Load Balancer(簡稱LB)后面所有的機器,Ribbon會自動的幫助你基于某種規則(如簡單輪詢,隨即連接等)去連接這些機器。我們也很容易使用Ribbon實現自定義的負載均衡算法。
說起負載均衡一般都會想到服務端的負載均衡,常用產品包括LBS硬件或云服務、Nginx等,都是耳熟能詳的產品。
而Spring Cloud提供了讓服務調用端具備負載均衡能力的Ribbon,通過和Eureka的緊密結合,不用在服務集群內再架設負載均衡服務,很大程度簡化了服務集群內的架構。
具體也不想多寫虛的介紹,反正哪里都能看得到相關的介紹。
直接開擼代碼,通過代碼來看Ribbon是如何實現的。
配置
詳解:
1.RibbonAutoConfiguration配置生成RibbonLoadBalancerClient實例。
代碼位置:
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()); } // 略 }
先看配置條件項,RibbonAutoConfiguration配置必須在LoadBalancerAutoConfiguration配置前執行,因為在LoadBalancerAutoConfiguration配置中會使用RibbonLoadBalancerClient實例。
RibbonLoadBalancerClient繼承自LoadBalancerClient接口,是負載均衡客戶端,也是負載均衡策略的調用方。
2.LoadBalancerInterceptorConfig配置生成:
1).負載均衡攔截器LoadBalancerInterceptor實例
包含:
LoadBalancerClient實現類的RibbonLoadBalancerClient實例
負載均衡的請求創建工廠LoadBalancerRequestFactory:實例
2).RestTemplate自定義的RestTemplateCustomizer實例
代碼位置:
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); } }; } } // 略 }
先看配置條件項:
要求在項目環境中必須要有RestTemplate類。
要求必須要有LoadBalancerClient接口的實現類的實例,也就是上一步生成的RibbonLoadBalancerClient。
3.通過上面一步創建的RestTemplateCustomizer配置所有RestTemplate實例,就是將負載均衡攔截器設置給RestTemplate實例。
@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)這個地方就是注入負載均衡攔截器的地方LoadBalancerInterceptor。
從這個地方實際上也可以猜出來,RestTemplate可以通過注入的攔截器來構建相應的請求實現負載均衡。
也能看出來可以自定義攔截器實現其他目的。
4.RibbonClientConfiguration配置生成ZoneAwareLoadBalancer實例
代碼位置:
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接口,該接口有一個方法:
/** * 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就是一個具體的負載均衡實現類,也是默認的負載均衡類,通過對chooseServer方法的實現選取某個服務實例。
攔截&請求
1.使用RestTemplate進行Get、Post等各種請求,都是通過doExecute方法實現
代碼位置:
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請求方法最終都是調用doExecute方法,該方法內調用創建方法創建請求實例,并執行請求得到響應對象。
2.生成請求實例創建工廠
上一步代碼中,調用createRequest方法創建請求實例,這個方法是定義在父類中。
先整理出主要的繼承關系:
createRequest方法實際是定義在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方法中調用getRequestFactory方法獲得請求實例創建工廠,實際上getRequestFactory并不是當前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; } } }
在這里做了個小動作,首先還是通過HttpAccessor類創建并獲得SimpleClientHttpRequestFactory工廠,這個工廠主要就是在沒有攔截器的時候創建基本請求實例。
其次,在有攔截器注入的情況下,創建InterceptingClientHttpRequestFactory工廠,該工廠就是創建帶攔截器的請求實例,因為注入了負載均衡攔截器,所以這里就從InterceptingClientHttpRequestFactory工廠創建。
3.通過工廠創建請求實例
創建實例就看工廠的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了個InterceptingClientHttpRequest實例,并且把攔截器、基本請求實例創建工廠注進去。
4.請求實例調用配置階段注入的負載均衡攔截器的攔截方法intercept
可從第1步看出,創建完請求實例后,通過執行請求實例的execute方法執行請求。
ClientHttpRequest request = createRequest(url, method); if (requestCallback != null) { requestCallback.doWithRequest(request); } response = request.execute();
實際請求實例是InterceptingClientHttpRequest,execute實際是在它的父類中。
類定義位置:
spring-web-4.3.12.RELEASE.jar
org.springframework.http.client
InterceptingClientHttpRequest.class
看一下它們的繼承關系。
在execute方法中實際調用了子類實現的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; }
其實就是InterceptingClientHttpRequest類的executeInternal方法,其中,又調用了一個執行器InterceptingRequestExecution的execute,通關判斷如果有攔截器注入進來過,就調用攔截器的intercept方法。
這里的攔截器實際上就是在配置階段注入進RestTemplate實例的負載均衡攔截器LoadBalancerInterceptor實例,可參考上面配置階段的第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.負載均衡攔截器調用負載均衡客戶端
在負載均衡攔截器LoadBalancerInterceptor類的intercept方法中,又調用了負載均衡客戶端LoadBalancerClient實現類的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步,可以看到實現類是RibbonLoadBalancerClient。
6.負載均衡客戶端調用負載均衡策略選取目標服務實例并發起請求
在RibbonLoadBalancerClient的第一個execute方法以及getServer方法中可以看到,實際上是通過ILoadBalancer的負載均衡器實現類作的chooseServer方法選取一個服務,交給接下來的請求對象發起一個請求。
這里的負載均衡實現類默認是ZoneAwareLoadBalancer區域感知負載均衡器實例,其內部通過均衡策略選擇一個服務。
ZoneAwareLoadBalancer的創建可以參考配置階段的第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; } // 略 } }
代碼擼完,總結下。
普通使用RestTemplate請求其他服務時,內部使用的就是常規的http請求實例發送請求。
為RestTemplate增加了@LoanBalanced 注解后,實際上通過配置,為RestTemplate注入負載均衡攔截器,讓負載均衡器選擇根據其對應的策略選擇合適的服務后,再發送請求。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。