前言:
Ribbon作爲客戶端負載均衡的一種手段,被廣泛應用在微服務項目中。
有關於Ribbon的介紹和使用方式,讀者可參考筆者的另一篇文章 https://blog.csdn.net/qq_26323323/article/details/78668776
本文主要介紹基於@LoadBalanced的RestTemplate來實現的負載均衡的源碼解析
1.@LoadBalanced分析
maven引入ribbon依賴後,
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>1.3.1.RELEASE</version>
</dependency>
只需要在RestTemplate的bean上面加入@LoadBalanced註解即可在使用RestTemplate發送HTTP請求時,自動實現負載均衡調用
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
由此可見,主要的實現應該就在@LoadBalanced上,那我們就來分析下其源碼
@LoadBalanced源碼如下:
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
再次讓大家失望了,這個註解沒有什麼特別之處,也沒有像往常的註解引入其他類。按照常規慣例,我們就到引入的jar包下看下META-INF/spring.factories文件有沒有引入什麼特別的類。我們就到@LoadBalanced所在的jar包spring-cloud-commons-1.2.2.RELEASE-sources.jar來看下其spring.factories文件
# AutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.client.CommonsClientAutoConfiguration,\
org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration,\
org.springframework.cloud.client.hypermedia.CloudHypermediaAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\
org.springframework.cloud.client.serviceregistry.ServiceRegistryAutoConfiguration,\
org.springframework.cloud.commons.util.UtilAutoConfiguration,\
org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClientAutoConfiguration
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.client.HostInfoEnvironmentPostProcessor
AutoConfiguration引入的類都會在項目啓動時被添加到Spring容器中,與LoadBalanced相關的的有兩個,直覺上LoadBalancerAutoConfiguration更像我們需要看的類(當然,不是直覺,是筆者看了類源碼之後寫的決定,哈哈),那麼我們來詳細分析下這個類,看看是否有驚喜
2.LoadBalancerAutoConfiguration
* Copyright 2013-2017 the original author or authors.
package org.springframework.cloud.client.loadbalancer;
...
@Configuration
@ConditionalOnClass(RestTemplate.class)// 當前環境需要有RestTemplate.class
@ConditionalOnBean(LoadBalancerClient.class)// 需要當前環境有LoadBalancerClient接口的實現類
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)// 初始化賦值LoadBalancerRetryProperties
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
// 1.@AutoWired也會自動裝載集合類list,會將合適的RestTemplate添加到restTemplates中
// 而至於加載哪些RestTemplate,就是標註了@LoadBalanced的RestTemplate
// 上面我們看到@LoadBalanced有一個@Qualifier就是特殊標註的含義,所以普通的沒有添加@LoadBalanced
// 則不會被添加到restTemplates中的
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
// 2.SmartInitializingSingleton接口的實現類會在項目初始化之後被調用其afterSingletonsInstantiated方法
//
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);
}
}
}
};
}
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
@Bean
@ConditionalOnMissingBean
// 3.LoadBalancerRequestFactory被創建
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
}
@Configuration
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
// 4.將LoadBalancerClient接口的實現類和3方法中創建的LoadBalancerRequestFactory
// 注入到該方法中,同時成爲LoadBalancerInterceptor的參數
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
// 5. 方法4中創建的LoadBalancerInterceptor會被作爲方法參數注入進來
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return new RestTemplateCustomizer() {
@Override
// 5.1 customize方法會被2方法中的afterSingletonsInstantiated()遍歷調用
public void customize(RestTemplate restTemplate) {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}
}
// 有關於RetryTemplate相關的bean在該例中不會被加載進來
@Configuration
@ConditionalOnClass(RetryTemplate.class)
static class RetryAutoConfiguration {
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate template = new RetryTemplate();
template.setThrowLastExceptionOnExhausted(true);
return template;
}
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory() {
return new LoadBalancedRetryPolicyFactory.NeverRetryFactory();
}
@Bean
public RetryLoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
LoadBalancedRetryPolicyFactory lbRetryPolicyFactory,
LoadBalancerRequestFactory requestFactory) {
return new RetryLoadBalancerInterceptor(loadBalancerClient, retryTemplate(), properties,
lbRetryPolicyFactory, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
return new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}
}
}
總結:經過以上的一堆註釋可知,該類的主要作用就是給添加了@LoadBalanced註解的RestTemplate類,添加攔截器LoadBalancerInterceptor,該攔截器攔截到請求後將請求重新處理,就在這個攔截器中實現了負載均衡的相關功能。
疑問:上文中一直有有LoadBalancerClient,那麼這個接口的實現類是什麼呢?讀者可先自行思考下
3.RestTemplate.getForObject(String url, Class<T> responseType, Object... uriVariables)
經過上面的一堆解釋,我們知道,新創建的RestTemplate在項目啓動完成之後,會被添加一個新的攔截器,在我們發出具體的HTTP請求時,攔截器會攔截該請求,並真正發揮負載均衡的作用,那麼我們來看下,RestTemplate發出get請求的具體操作
getForObject()源碼如下:
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.創一個request請求
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
// 2.執行該請求
response = request.execute();
// 3.對響應結果進行封裝
handleResponse(url, method, response);
if (responseExtractor != null) {
return responseExtractor.extractData(response);
}
else {
return null;
}
}
...
}
主要就分三步,下面逐個來分析下
1)createRequest(url, method)創一個request請求
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;
}
//getRequestFactory
public ClientHttpRequestFactory getRequestFactory() {
ClientHttpRequestFactory delegate = super.getRequestFactory();
// 我們的RestTemplate攔截器不爲空
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) {
// 最終返回的就是這個request
return new InterceptingClientHttpRequest(requestFactory, this.interceptors, uri, httpMethod);
}
2)request.execute()執行該請求
當前的request爲InterceptingClientHttpRequest
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
// 1.iterator即攔截器集合
// private final Iterator<ClientHttpRequestInterceptor> iterator;
if (this.iterator.hasNext()) {
ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
// 2.逐個攔截器來執行,我們就看最重要的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();
}
}
}
總結2):重點就是執行攔截器的intercept方法,下面我們來看下LoadBalancerInterceptor.intercept()方法
4.LoadBalancerInterceptor.intercept()執行負載均衡攔截器方法
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);
// 真正執行的方法
// private LoadBalancerClient loadBalancer; LoadBalancerClient默認實現類爲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.根據用戶請求的serviceId來獲取具體的LoadBalanced
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// 2.獲取具體的server(也就是定位到哪臺服務器的哪個端口號的具體服務信息)
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.執行HTTP請求
return execute(serviceId, ribbonServer, request);
}
注意:通過這個方法的分析可以看到,裏面通過一系列的算法根據用戶輸入的serviceId(也就是服務名)來獲取到具體的服務所在host、port,然後重新封裝HTTP請求,最後執行該HTTP請求即可
下面逐個方法來分析
1)getLoadBalancer()
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) {
// 主要就是這句,從容器中獲取ILoadBalancer的實現,目前默認實現爲
// ZoneAwareLoadBalancer
return context.getBean(type);
}
return null;
}
總結1):通過上述一連串的方法調用可知,在最終是從容器中獲取ILoadBalancer的實現,目前默認實現爲ZoneAwareLoadBalancer
疑問:ZoneAwareLoadBalancer是什麼時候被加載到容器中呢?讀者可自行思考下
2)getServer(loadBalancer)
protected Server getServer(ILoadBalancer loadBalancer) {
if (loadBalancer == null) {
return null;
}
// 具體執行爲ZoneAwareLoadBalancer.chooseServer()
return loadBalancer.chooseServer("default"); // TODO: better handling of key
}
// ZoneAwareLoadBalancer.chooseServer()
public Server chooseServer(Object key) {
// 1.由於筆者測試的server,可用的zone爲1個,所以會直接走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);
}
//如果是多region,則會走下面的方法,暫時註釋掉
...
}
//BaseLoadBalancer.chooseServer()
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
// rule爲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();
// 重點在這裏,從所有的server中根據對應的rule來獲取一個具體的server
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
總結2):getServer()的主要功能就是根據具體的rule來選擇特定的Server,重要的實現實際都在這個方法裏
鑑於篇幅,筆者沒有分析其他的規則使用,只分析當前案例中的規則
3)RibbonLoadBalancerClient.execute(serviceId, ribbonServer, request)執行HTTP請求
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 {
// 核心方法在這裏,是一個回調方法,
// 具體就是回調LoadBalancerRequestFactory.createRequest()中的apply()方法
T returnVal = request.apply(serviceInstance);
statsRecorder.recordStats(returnVal);
return returnVal;
}
...
}
// 回調方法
public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request,
final byte[] body, final ClientHttpRequestExecution execution) {
return new LoadBalancerRequest<ClientHttpResponse>() {
@Override
// 回調方法在這裏
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);
}
}
// 真正要執行的方法
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);
}
// 注意:此時已經沒有iterator,直接執行request請求
else {
// 1.根據URI獲得請求,並封裝頭部
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.本質就是對HttpURLConnection的執行
return delegate.execute();
}
}
}
到此爲止,URI的請求基本已經結束了。
總結:
1)用戶創建RestTemplate
2)添加了ribbon依賴後,會在項目啓動的時候自動往RestTemplate中添加LoadBalancerInterceptor攔截器
3)用戶根據RestTemplate發起請求時,會將請求轉發到LoadBalancerInterceptor去執行,該攔截器會根據指定的負載均衡方式獲取該次請求對應的應用服務端IP、port
4)根據獲取到的IP、port重新封裝請求,發送HTTP請求,返回具體響應
參考:SpringCloud微服務實戰