RestTemplate如何通过服务名进行微服务间的调用?
在使用Spring Cloud开发微服务应用的过程中,不可避免的会遇到微服务间的调用。微服务间的相互调用主要通过两种方式:FeignRestTemplateFeign的方式本文暂不讨论,而RestTemplate本质上是对OkHttp/HttpClient做了一层封装,通过服务地址向微服务发起HTTP请求没问题,但是又是如何做到,通过服务名进行微服务间的访问的呢?其中的原理,接下来我们会详细阐述。
在使用Spring Cloud开发微服务应用的过程中,不可避免的会遇到微服务间的调用。微服务间的相互调用主要通过两种方式:
-
Feign
-
RestTemplate
Feign的方式本文暂不讨论,而RestTemplate本质上是对OkHttp/HttpClient做了一层封装,通过服务地址向微服务发起HTTP请求没问题,但是又是如何做到,通过服务名进行微服务间的访问的呢?其中的原理,接下来我们会详细阐述。
1.1 启用RestTemplate
@LoadBalanced注解修饰的RestTemplate才能实现服务名的调用,没有修饰的RestTemplate是没有该功能的,只能使用IP、Port访问微服务。
在配置类中注入RestTemplate:
@Bean
@LoadBalanced
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
return new RestTemplate(factory);
}
然后,即可通过http://SERVICE-NAME/quest-path访问对应名称的微服务。
1.2 @LoadBalanced的作用
@LoadBalanced是Netflix的ribbon中的一个负载均衡的注解,并完成以下工作:
-
从负载均衡器中选一个对应的服务实例,所有的服务名实例都放在负载均衡器中的serverlist中;
-
从挑选的实例中去请求内容;
-
由服务名转为真正使用的ip地址;
1.3 RestTemplate使用服务名访问服务的流程
通过Debug跟踪请求,我们画出了RestTemplate通过服务名访问微服务的流程:
通过源码跟踪可知,restTemplate能通过服务名获取到具体的服务,是由LoadBalancerInterceptor这个拦截器实现的,而具体的工作是由RibbonLoadBalancerClient来完成的。
RibbonLoadBalancerClient将服务名通过负载均衡策略转为了实际的ip和端口后再apply给restTemplate。
1.4 关键源码分析
1.4.1 LoadBalancerInterceptor拦截器何时触发?
InterceptingClientHttpRequest类中:
@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();
//LoadBalancerInterceptor拦截器触发
return nextInterceptor.intercept(request, body, this);
}
//服务地址(IP+Port)请求
else {
HttpMethod method = request.getMethod();
Assert.state(method != null, "No standard HTTP method");
ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
if (body.length > 0) {
if (delegate instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
}
else {
StreamUtils.copy(body, delegate.getBody());
}
}
return delegate.execute();
}
}
}
1.4.2 何时获取的服务名?
LoadBalancerInterceptor类中:
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
1.4.3 何时获取的服务实例?
RibbonLoadBalancerClient类中:
@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(String serviceId) {
return getServer(getLoadBalancer(serviceId));
}
protected Server getServer(ILoadBalancer loadBalancer) {
if (loadBalancer == null) {
return null;
}
return loadBalancer.chooseServer("default"); // TODO: better handling of key
}
1.4.4 LoadBalancerInterceptor是何时被设置到restTemplate中的?
/**
* Auto configuration for Ribbon (client side load balancing).
*
* @author Spencer Gibb
* @author Dave Syer
* @author Will Tran
*/
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
......
@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);
}
};
}
}
......
}
1.5 总结
至此,RestTemplate通过服务名访问微服务的整个流程已介绍完毕,至于LoadBanlancer的具体实现操作是怎么样的?service的列表是何时加载到这个框架的?等等,感兴趣的可以自己去探究。
如果你喜欢本文,欢迎点击右上角,把文章分享到朋友圈.....
关注coder2plus,获取技术干货、IT圈趣闻,汲取前人之经验,做自己之法宝。
技术交流群,请加QQ:coder++(193953995),Wechat:coder2plus;
投稿、合作、版权等,请联系邮箱:coder_plus@qq.com;
更多推荐
所有评论(0)