0


Spring Cloud 总结 - 调用远程服务的三种方式及原理分析

一个简单的微服务架构图

本文设计的 Spring Cloud 版本以及用到的 Spring Cloud 组件

  • Spring Cloud Hoxton.SR5
  • eureka
  • feign
  • ribbon

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t7Zol1ve-1671354198915)(/Users/zhanggaopei/Library/Application Support/typora-user-images/image-20221218131504255.png)]

后面的内容都将围绕上面的图来分析.

调用远程服务的三种方式

在 Spring Cloud 服务架构中, 一个服务可能部署多个实例, 通常情况下, 这个时候请求一个服务接口, 是需要通过 服务名 去调用的, 比如:

http://user-service/getUser

.

然后在 外力 的帮助下, 通过服务名拿到多个实例的地址列表, 再借助负载均衡算法, 从地址列表中选择一个具体的地址, 发送 HTTP 请求.

具体的做法分为如下三种:

一、基于 RestTemplate 和 @LoadBalanced 注解

RestTemplate

spring-web 包提供的, 用来调用 HTTP 接口的工具类, 它提供了

GET

POST

等常用的请求方法.使用方式如下:

  1. 添加到 spring 容器
@BeanpublicRestTemplaterestTemplate(){returnnewRestTemplate();}
  1. 使用前注入依赖
@AutowiredprivateRestTemplate restTemplate;
  1. 常用 API
// 发送 GET 请求
restTemplate.getForObject(...)// 发送 POST 请求
restTemplate.postForObject(...)// 自定义
restTemplate.execute(...)

按照上面那种简单的写法, 我们只能调用有明确 IP端口 的接口, 要想实现我们的需求, 至少要做两件事情:

  1. 根据服务名拿到服务实例的信息
  2. 负载均衡算法
RestTemplate

提供了拦截器的功能

ClientHttpRequestInterceptor

, 开发者可以 手动编码 实现上面两个功能. Spring Cloud 已经帮我们实现了这个功能.使用方式如下:

  1. 在原有基础上加上 @LoadBalanced 注解
@Bean@LoadBalancedpublicRestTemplaterestTemplate(){returnnewRestTemplate();}
  1. 调用接口时,传入服务名称
User user = restTemplate.getForObject("http://user-service/getUser",User.class);

一个注解就帮我们完成了负载均衡.

二、基于 DiscoveryClient

org.springframework.cloud.client.discovery.DiscoveryClient

可以帮我们实现服务发现的功能, 只要我们拿到服务对应的实例信息, 后面 负载均衡 可以手动编码实现.

  1. 注入依赖
@AutowiredprivateDiscoveryClient discoveryClient;
  1. 获取注册中心服务实例列表
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
  1. 选取一个实例的地址信息, 发送请求

三、基于 Feign 的声明式调用

  1. 在启动类上加对应的注解.
@EnableFeignClients
  1. 声明接口
@FeignClient("user-service")publicinterfaceUserFeignClient{@GetMapping("/getUser")UsergetUser();}

原理分析

关于源码分析部分, 本文并不会逐行分析, 只会把 关键方法 注释说明(如果读者自行 debug, 是不会迷路的.), 中间很多无聊的方法跳转的过程都省略了.

RestTemplate 与 @LoadBalanced 注解的带来的 “化学反应”

先看一下大致的实现思路.

在这里插入图片描述

1. 以 @LoadBalanced 为入口开启源码之旅

源码注释的大概意思是, 在

RestTemplate

上加上这个注解, 就能使用

LoadBalancerClient

接口做一些事情, 通过查看这个接口的注释, 它能提供的能力跟负载均衡相关.

所以,到这里我们已经清楚的了解到

@LoadBalanced

注解能为我们提供 负载均衡 的能力, 下面就需要弄清楚底层是如何实现负载均衡的.

Annotation to mark a RestTemplate or WebClient bean to be configured to use a LoadBalancerClient

通过查看源代码, 我们在如下两个地方看到了 @LoadBalanced 的使用, 通过调试发现, 断点根本没有走到第二个地方.

publicclassLoadBalancerAutoConfiguration{@LoadBalanced@Autowired(required =false)privateList<RestTemplate> restTemplates =Collections.emptyList();}
publicclassLoadBalancerWebClientBuilderBeanPostProcessorimplementsBeanPostProcessor{@OverridepublicObjectpostProcessBeforeInitialization(Object bean,String beanName)throwsBeansException{if(bean instanceofWebClient.Builder){if(context.findAnnotationOnBean(beanName,LoadBalanced.class)==null){return bean;}((WebClient.Builder) bean).filter(exchangeFilterFunction);}return bean;}}

所以我们还是要把目光聚焦到下面的源代码:

publicclassLoadBalancerAutoConfiguration{@LoadBalanced@Autowired(required =false)privateList<RestTemplate> restTemplates =Collections.emptyList();}

这里我通过描述这块代码的逻辑, 来引出一个有趣的 Spring 相关的知识点(关于这个知识点原理, 可以先直接跳到文末 Spring @Qualifier 注解的妙用):

首先, 我们应该知道, 通过如下方式, 我们可以把 Spring 容器中的所有

RestTemplate

类型的

Bean

对象添加到下面的集合中.

@AutowiredprivateList<RestTemplate> restTemplates =Collections.emptyList();

而我们在上面的基础上再加上

@LoadBalanced

注解, 那么这个集合收集的元素就加了一层限制条件, 集合中的

Bean

不仅要是

RestTemplate

类型, 而且

Bean

在声明时, 必须加上

@LoadBalanced

注解, 比如下面的声明方式:

@Bean@LoadBalancedpublicRestTemplaterestTemplate(){returnnewRestTemplate();}

然后我们接着看 Spring Cloud 如何对

RestTemplate

进行加工的

publicclassLoadBalancerAutoConfiguration{@LoadBalanced@Autowired(required =false)privateList<RestTemplate> restTemplates =Collections.emptyList();@Autowired(required =false)privateList<LoadBalancerRequestTransformer> transformers =Collections.emptyList();// 第一步: 遍历 restTemplates 集合@BeanpublicSmartInitializingSingletonloadBalancedRestTemplateInitializerDeprecated(finalObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers){return()-> restTemplateCustomizers.ifAvailable(customizers ->{for(RestTemplate restTemplate :LoadBalancerAutoConfiguration.this.restTemplates){// RestTemplateCustomizer#customizefor(RestTemplateCustomizer customizer : customizers){
                    customizer.customize(restTemplate);}}});}@Configuration(proxyBeanMethods =false)@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")staticclassLoadBalancerInterceptorConfig{// 第二步: 进行自定义操作, 也就是把 LoadBalancerInterceptor 这个我们文章开头提到的拦截器设置进去.@Bean@ConditionalOnMissingBeanpublicRestTemplateCustomizerrestTemplateCustomizer(finalLoadBalancerInterceptor loadBalancerInterceptor){return restTemplate ->{List<ClientHttpRequestInterceptor> list =newArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);};}}

到此为止, 程序启动前的一些关键步骤已经搞清楚了, 下面继续分析调用流程.

2. 请求调用流程

源码入口:

User user = restTemplate.getForObject("http://user-service/getUser",User.class);

顺着

getForObject

进到关键方法

publicclassRestTemplate{// doExecuteprotected<T>TdoExecute(URI url,@NullableHttpMethod method,@NullableRequestCallback requestCallback,@NullableResponseExtractor<T> responseExtractor)throwsRestClientException{// 创建请求对象// 这里最终其实通过 InterceptingClientHttpRequestFactory#createRequest 方法// 创建了 InterceptingClientHttpRequest ClientHttpRequest request =createRequest(url, method);
            response = request.execute();}}

紧接着 看

InterceptingClientHttpRequest

execute

方法

classInterceptingClientHttpRequestextendsAbstractBufferingClientHttpRequest{// 第一步: 执行完父类的 execute 方法后, 会来到这里.@OverrideprotectedfinalClientHttpResponseexecuteInternal(HttpHeaders headers,byte[] bufferedOutput)throwsIOException{InterceptingRequestExecution requestExecution =newInterceptingRequestExecution();return requestExecution.execute(this, bufferedOutput);}privateclassInterceptingRequestExecutionimplementsClientHttpRequestExecution{privatefinalIterator<ClientHttpRequestInterceptor> iterator;publicInterceptingRequestExecution(){this.iterator = interceptors.iterator();}// 第二步: 先 执行前面设置的拦截器 LoadBalancerInterceptor 通过 服务名, + 负载均衡 , 拿到其中一个实例的请求地址.// 然后根据真实的地址, 发送 http 请求.@OverridepublicClientHttpResponseexecute(HttpRequest request,byte[] body)throwsIOException{// 先 执行前面设置的拦截器 LoadBalancerInterceptor 通过 服务名, + 负载均衡 , 拿到其中一个实例的请求地址.
                nextInterceptor.intercept(request, body,this);// 然后根据真实的地址, 发送 http 请求.  AbstractClientHttpRequest#execute      return delegate.execute();}}}}
LoadBalancerInterceptor

的负载均衡处理

到这里, 我们就可以回答开头提到的问题:

@LoadBalanced

是如何给

RestTemplate

提供负载均衡能力的, 众所周知 Ribbon 的能力就 负载均衡.

源码再往后看就是 Ribbon 的领域了, 我们不再继续深究. 后面可以单独写一篇文章对 Ribbon 的原理和源码进行分析.

publicclassLoadBalancerInterceptorimplementsClientHttpRequestInterceptor{// 看到这里, 我们应该回想到一开始说的, @LoadBalanced 注解的相关注释说明.// 加上 @LoadBalanced 注解, 我们就能给 RestTemplate 赋予负载均衡的能力.privateLoadBalancerClient loadBalancer;@OverridepublicClientHttpResponseintercept(finalHttpRequest request,finalbyte[] body,finalClientHttpRequestExecution execution)throwsIOException{// 因为我们集成了 Ribbon、 所以这里 loadBalancer 就是 RibbonLoadBalancerClientreturnthis.loadBalancer.execute(serviceName,this.requestFactory.createRequest(request, body, execution));}}

Spring @Qualifier 注解的妙用

/**
 * This annotation may be used on a field or parameter as a qualifier for
 * candidate beans when autowiring. It may also be used to annotate other
 * custom annotations that can then in turn be used as qualifiers.
 */@Target({ElementType.FIELD,ElementType.METHOD,ElementType.PARAMETER,ElementType.TYPE,ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic@interfaceQualifier{Stringvalue()default"";}

不管是根据上面的注释, 还是我们的使用经验来讲, 我们都应该知道

@Qualifier

这个注解:它起到的是限定, “精确匹配”

Bean

的作用,比如: 当同一类型的

Bean

有多个不同实例时,可通过此注解来做 筛选或匹配

然后再来看下这个注解的一段注释:

It may also be used to annotate other custom annotations that can then in turn be used as qualifiers.

简单翻一下就是:

@Qualifier

可以注解其他 自定义的注解, 然后这些 自定义注解 就可以反过来为我们注入 Bean 时, 起到限定的作用(上面已经讲过它限定了什么).

于是我们再回过头看下

@LoadBalanced

注解源码:

@Target({ElementType.FIELD,ElementType.PARAMETER,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Qualifierpublic@interfaceLoadBalanced{}

从上可以看出, 这个自定义注解上是包含

@Qualifier

, 所以

@LoadBalanced

注解是可以在我们注入 bean 时, 起到限定作用的.

关于

@Qualifier

详细的源码和原理分析 可以围绕

QualifierAnnotationAutowireCandidateResolver

这个类做检索, 这里不再详细阐述.


本文转载自: https://blog.csdn.net/cnm10050/article/details/128363778
版权归原作者 nimo10050 所有, 如有侵权,请联系我们删除。

“Spring Cloud 总结 - 调用远程服务的三种方式及原理分析”的评论:

还没有评论