springCloud的[email
學習springcluod的時候,有一個困惑,為什麼RestTemplate上面@LoadBalanced註解,就能實現負載均衡,今天我們一起學習下原始碼,探索下springCloud底層的祕密:
第一步:在看原始碼之前我們先自己搭建一個消費者微服務(因為我們這裡主要講解的是springCloud的Ribbon負載均衡,所以註冊中心和提供者這裡就不再講解了)
1、引入必要的maven依賴:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> <relativePath /> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Edgware.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> </dependency> </dependencies>
2、建立springboot的啟動類:
@SpringBootApplication @EnableDiscoveryClient public class HelloApplicaton {
//這裡就是建立一個負載均衡的RestTemplate Bean @LoadBalanced @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(HelloApplicaton.class, args); } }
3、resource下配置啟動檔案:application.yml或者application.properties(具體的配置檔案的作用我覺得應該不用我做解釋)
server: port: 9091 spring: application: name: post-service eureka: instance: hostname: localhost client: serviceUrl: defaultZone: http://localhost:1111/eureka/
4、建立消費者請求介面:
@RestController public class HelloController {
//注入前面建立的負載均衡的RestTemplate @Autowired public RestTemplate restTemplate; @RequestMapping(value = "/hello", method = RequestMethod.GET) public String postHello(){ //使用有負載均衡能力的RestTemplate請求微服務 return restTemplate.getForEntity("http://HELLO-SERVICE/hello" ,String.class,"").getBody(); } }
注:這裡的HELLO-SERVICE就是註冊中心存在的微服務的名稱,這個大家應該都很清楚;
這樣一個具有負載均衡能力的消費者介面就建立成功了!
但是為什麼被@LoadBalanced註解修飾的RestTemplate就有了負載均衡的能力呢,先看看@LoadBalanced註解:
/** * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient * @author Spencer Gibb */ @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Qualifier public @interface LoadBalanced { }
這就是一個普通的標記註解,作用就是修飾RestTemplate讓其擁有負載均衡的能力,檢視org.springframework.cloud.client.loadbalancer包下面的class,我們很容易發現LoadBalancerAutoConfiguration這個類;
@Configuration//這是一個配置類@ConditionalOnClass(RestTemplate.class)//這個配置檔案載入必要條件是存在RestTemplate類和LoadBalancerClient //Bean @ConditionalOnBean(LoadBalancerClient.class) @EnableConfigurationProperties(LoadBalancerRetryProperties.class) public class LoadBalancerAutoConfiguration {
//這個很重要,這裡的restTemplates是所有的被@LoadBalanced註解的集合,這就是標記註解的作用(Autowired是可以集合注入的) @LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList(); @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); } } } }; } @Autowired(required = false) private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList(); @Bean @ConditionalOnMissingBean public LoadBalancerRequestFactory loadBalancerRequestFactory( LoadBalancerClient loadBalancerClient) { return new LoadBalancerRequestFactory(loadBalancerClient, transformers); }
//生成一個LoadBalancerInterceptor的Bean @Configuration @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class LoadBalancerInterceptorConfig { @Bean public LoadBalancerInterceptor ribbonInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) { return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); }
//給註解了@LoadBalanced的RestTemplate加上攔截器 @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); } }; } }
}
總結:現在我們應該大致知道@loadBalanced的作用了,就是起到一個標記RestTemplate的作用,當服務啟動時,標記了的RestTemplate物件裡面就會被自動加入LoadBalancerInterceptor攔截器,這樣當RestTemplate像外面發起http請求時,會被LoadBalancerInterceptor的intercept函式攔截,而intercept裡面又呼叫了LoadBalancerClient介面實現類execute方法,我們接著往下看;
LoadBalancerInterceptor的intercept方法:
@Override public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
//這是以服務名為地址的原始請求:例:http://HELLO-SERVICE/hello 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)); }
這裡的LoadBalancerClient的實現是RibbonLoadBalancerClient,
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
//通過serviceId找到ILoadBalancer 的實現者,預設是ZoneAwareLoadBalancer
//(RibbonClientConfiguration裡面對ILoadBalancer的實現做的預設配置)ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId); Server server = this.getServer(loadBalancer); if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } else {
//將Server組裝成RibbonServer,附加了一些額外的資訊 RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server)); return this.execute(serviceId, ribbonServer, request); } }
//ZoneAwareLoadBalancer的Rule規則去選擇Server(這裡的規則是統一個zone的優先選擇,這裡返回的Server
//就是通過serviceId和Rule解析返回的帶有IP:port形式的資訊的Server)
protected Server getServer(ILoadBalancer loadBalancer) { return loadBalancer == null ? null : loadBalancer.chooseServer("default"); }
接著往下看:
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException { Server server = null; if (serviceInstance instanceof RibbonLoadBalancerClient.RibbonServer) { server = ((RibbonLoadBalancerClient.RibbonServer)serviceInstance).getServer(); } if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } else {
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 var8) { statsRecorder.recordStats(var8); throw var8; } catch (Exception var9) { statsRecorder.recordStats(var9); ReflectionUtils.rethrowRuntimeException(var9); return null; } } }
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); } }; }
這裡需要注意ServiceRequestWrapper物件改寫了getURL方法:
@Override public URI getURI() { URI uri = this.loadBalancer.reconstructURI( this.instance, getRequest().getURI()); return uri; }
所以這裡的reconstructURI實際上呼叫的是實現類RibbonLoadBalancerClient的reconstructURI方法:
public URI reconstructURI(ServiceInstance instance, URI original) { Assert.notNull(instance, "instance can not be null"); String serviceId = instance.getServiceId(); RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
//實際請求的server還是以IP:port的形式 Server server = new Server(instance.getHost(), instance.getPort()); IClientConfig clientConfig = this.clientFactory.getClientConfig(serviceId); ServerIntrospector serverIntrospector = this.serverIntrospector(serviceId);
//是否需要轉換為Https URI uri = RibbonUtils.updateToHttpsIfNeeded(original, clientConfig, serverIntrospector, server);
// 根據Server組裝真正的URI
return context.reconstructURIWithServer(server, uri);
}
execution.execute(serviceRequest, body)方法呼叫的是InterceptingRequestExecution的execute方法:
@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();
}
}
}
這裡的request.getURI()呼叫的就是ServiceRequestWrapper的getURL,也就是RibbonLoadBalancerClient的reconstructURI方法;總結:好了,至此springCloud的分析告一段落,這裡也只是給給出了負載均衡的大綱,沒有列出詳細的細節,讀者可根據大綱對照著檢視細節,感謝您的觀看,歡迎指錯;