在使用Spring Cloud开发微服务应用的过程中,不可避免的会遇到微服务间的调用。微服务间的相互调用主要通过两种方式:

  1. Feign

  2. 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中的一个负载均衡的注解,并完成以下工作:

  1. 从负载均衡器中选一个对应的服务实例,所有的服务名实例都放在负载均衡器中的serverlist中;

  2. 从挑选的实例中去请求内容;

  3. 由服务名转为真正使用的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

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐