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

news2024/12/22 20:06:16

一个简单的微服务架构图

本文设计的 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 注解

RestTemplatespring-web 包提供的, 用来调用 HTTP 接口的工具类, 它提供了 GETPOST 等常用的请求方法.使用方式如下:

  1. 添加到 spring 容器
@Bean
public RestTemplate restTemplate() {
  return new RestTemplate();
}
  1. 使用前注入依赖
@Autowired
private RestTemplate restTemplate;
  1. 常用 API
// 发送 GET 请求
restTemplate.getForObject(...)

// 发送 POST 请求
restTemplate.postForObject(...)
  
// 自定义
restTemplate.execute(...)

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

  1. 根据服务名拿到服务实例的信息

  2. 负载均衡算法

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

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

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

二、基于 DiscoveryClient

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

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

三、基于 Feign 的声明式调用

  1. 在启动类上加对应的注解.
@EnableFeignClients
  1. 声明接口
@FeignClient("user-service")
public interface UserFeignClient {

    @GetMapping("/getUser")
    User getUser();
}

原理分析

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

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

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

在这里插入图片描述

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

源码注释的大概意思是, 在 RestTemplate 上加上这个注解, 就能使用 LoadBalancerClient 接口做一些事情, 通过查看这个接口的注释, 它能提供的能力跟负载均衡相关.

所以,到这里我们已经清楚的了解到 @LoadBalanced 注解能为我们提供 负载均衡 的能力, 下面就需要弄清楚底层是如何实现负载均衡的.

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

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

public class LoadBalancerAutoConfiguration {

	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();
}
public class LoadBalancerWebClientBuilderBeanPostProcessor implements BeanPostProcessor {

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName)
			throws BeansException {
		if (bean instanceof WebClient.Builder) {
			if (context.findAnnotationOnBean(beanName, LoadBalanced.class) == null) {
				return bean;
			}
			((WebClient.Builder) bean).filter(exchangeFilterFunction);
		}
		return bean;
	}

}

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

public class LoadBalancerAutoConfiguration {

	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();
}

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

首先, 我们应该知道, 通过如下方式, 我们可以把 Spring 容器中的所有 RestTemplate 类型的 Bean 对象添加到下面的集合中.

@Autowired
private List<RestTemplate> restTemplates = Collections.emptyList();

而我们在上面的基础上再加上 @LoadBalanced 注解, 那么这个集合收集的元素就加了一层限制条件, 集合中的 Bean 不仅要是 RestTemplate 类型, 而且 Bean 在声明时, 必须加上 @LoadBalanced 注解, 比如下面的声明方式:

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
  return new RestTemplate();
}

然后我们接着看 Spring Cloud 如何对 RestTemplate 进行加工的

public class LoadBalancerAutoConfiguration {

	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();

	@Autowired(required = false)
	private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

  // 第一步: 遍历 restTemplates 集合
	@Bean
	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
			final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
		return () -> restTemplateCustomizers.ifAvailable(customizers -> {
			for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
        // RestTemplateCustomizer#customize
				for (RestTemplateCustomizer customizer : customizers) {
					customizer.customize(restTemplate);
				}
			}
		});
	}


	@Configuration(proxyBeanMethods = false)
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {

    // 第二步: 进行自定义操作, 也就是把 LoadBalancerInterceptor 这个我们文章开头提到的拦截器设置进去.
		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
				List<ClientHttpRequestInterceptor> list = new ArrayList<>(
						restTemplate.getInterceptors());
				list.add(loadBalancerInterceptor);
				restTemplate.setInterceptors(list);
			};
		}

	}

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

2. 请求调用流程

源码入口:

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

顺着 getForObject 进到关键方法

public class RestTemplate  {
  
  // doExecute
  protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
			@Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {

    	// 创建请求对象
      // 这里最终其实通过 InterceptingClientHttpRequestFactory#createRequest 方法
      // 创建了 InterceptingClientHttpRequest 
			ClientHttpRequest request = createRequest(url, method);
			response = request.execute();
		
	}
}

紧接着 看 InterceptingClientHttpRequestexecute 方法

class InterceptingClientHttpRequest extends AbstractBufferingClientHttpRequest {


  // 第一步: 执行完父类的 execute 方法后, 会来到这里.
	@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();
		}
		 
    // 第二步: 先 执行前面设置的拦截器 LoadBalancerInterceptor 通过 服务名, + 负载均衡 , 拿到其中一个实例的请求地址.
    // 然后根据真实的地址, 发送 http 请求.
		@Override
		public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
        // 先 执行前面设置的拦截器 LoadBalancerInterceptor 通过 服务名, + 负载均衡 , 拿到其中一个实例的请求地址.
				nextInterceptor.intercept(request, body, this);  
        // 然后根据真实的地址, 发送 http 请求.  AbstractClientHttpRequest#execute      
				return delegate.execute();
			}
		}
	}

}

LoadBalancerInterceptor 的负载均衡处理

到这里, 我们就可以回答开头提到的问题: @LoadBalanced 是如何给 RestTemplate 提供负载均衡能力的, 众所周知 Ribbon 的能力就 负载均衡.

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

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

  // 看到这里, 我们应该回想到一开始说的, @LoadBalanced 注解的相关注释说明.
  // 加上 @LoadBalanced 注解, 我们就能给 RestTemplate 赋予负载均衡的能力.
	private LoadBalancerClient loadBalancer;

	@Override
	public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
		
    // 因为我们集成了 Ribbon、 所以这里 loadBalancer 就是 RibbonLoadBalancerClient
		return this.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
@Documented
public @interface Qualifier {

	String value() 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
@Qualifier
public @interface LoadBalanced {

}

从上可以看出, 这个自定义注解上是包含 @Qualifier, 所以 @LoadBalanced 注解是可以在我们注入 bean 时, 起到限定作用的.

关于 @Qualifier详细的源码和原理分析 可以围绕 QualifierAnnotationAutowireCandidateResolver 这个类做检索, 这里不再详细阐述.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/100037.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

好玩的网站安利

好玩的网站安利 1、EGOUZ https://www.egouz.com/ 授人以鱼不如授人以渔&#xff0c;先给大家分享两个可以找国外优质网站的地方。EGOUZ是一个高质量的导航站&#xff0c;内容主要是外国网站&#xff0c;并且做了很详细的分类&#xff0c;网站资源非常丰富。 EGOUZ按照国家分…

50450-80-1,Ala-Ala-Ala-对硝基苯胺

AAA-pNA, chromogenic substrate for porcine pancreatic elastase and for astacin, a crayfish zinc-endopeptidase.3a - pna&#xff0c;猪胰腺弹性酶和阿斯塔星的显色底物&#xff0c;阿斯塔星是一种小龙虾锌内肽酶。 编号: 163840中文名称: 三肽Pancreatic elastase subst…

1573_AURIX_TC275_SCU中的急停功能以及overlay

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 急停功能是不需要软件进行干预的&#xff0c;至少&#xff0c;这个事件的动作触发是不需要软件进行干预的。急停事件的触发因素&#xff1a;外部的输入事件、SMU的警报信息。外部的输入事件…

概率论 ‖ Machine Learning必备知识

概率论&#xff01;Machine Learning必备知识 文章目录概率论&#xff01;Machine Learning必备知识1 什么是概率1.1 最简单的例子1.2 概率论与数理统计的关系2 大数定律和中心极限定理2.1 大数定律是什么&#xff1f;2.2 代码直观理解大数定律2.3 中心极限定理2.4 大数定律和中…

Java基础开发之编译异常和运行异常

一、getProperties()方法 1.System类提供一个getProperties()方法用来**当前系统的全部属性&#xff0c;它会返回一个Properties对象&#xff0c;也封装了系统的全部属性&#xff0c;这些属性的存在是以键值对的形式。 2.getProperties()方法使用例子&#xff1a; public cl…

《数字电子技术》笔记

《数字电子技术》笔记绪论第一章 信息和编码第二章 逻辑代数第三章 门电路第四章 组合电路的逻辑与分析第五章 触发器第七章 可编程逻辑器件绪论 什么是电子技术&#xff1f;是研究电子器件及电子器件应用的学科。EDA&#xff1a;Electronic Design Automation电子电路用处&am…

【Python机器学习】回归任务、线性回归评价指标讲解及温度与花朵数线性回归实战(图文解释 附源码)

需要源码和数据集请点赞关注收藏后评论区留言私信~~~ 与分簇、分类和标注任务不同&#xff0c;回归任务预测的不是有限的离散的标签值&#xff0c;而是无限的连续值。回归任务的目标是通过对训练样本的学习&#xff0c;得到从样本特征集到连续值之间的映射。如天气预测任务中&a…

ERC-3525 开发入门指南

ERC-3525 标准是以太坊社区批准通过的半匀质化通证&#xff08;Semifungible Token, 亦称为半同质化通证&#xff0c;简称 SFT&#xff09;标准&#xff0c;由 Solv Protocol 提出。ERC-3525 标准定义了一类新型的数字资产&#xff0c;具有以下突出优势&#xff1a;与 ERC-721 …

网曝某公司HR向求职者索要高考成绩!你们还记得自己的高考分数吗?

奇葩年年有&#xff0c;今年特别多&#xff0c;一位网友曝光&#xff1a;在应聘北京某公司的时候&#xff0c;HR竟然跟自己索要高考成绩&#xff01;网友感叹&#xff0c;遇到过在学信网查本科学位的公司&#xff0c;但这种奇葩公司没见过。有人说&#xff0c;虽然问高考成绩确…

git status 查看仓库文件状态

1. 前言 2. 各种状态 3. -s 参数 4. --ignored 查看所有被忽略的文件 1. 前言 git status 命令用于查看当前 git 中的文件状态 这个命令会将工作区、暂存区、版本库中的文件状态输出到命令行界面 git status git status 命令是 git 中最常用的命令之一&#xff0c;当我们要执…

[附源码]计算机毕业设计Python-菜篮子系统(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

C++11标准模板(STL)- 算法(std::equal)

定义于头文件 <algorithm> 算法库提供大量用途的函数&#xff08;例如查找、排序、计数、操作&#xff09;&#xff0c;它们在元素范围上操作。注意范围定义为 [first, last) &#xff0c;其中 last 指代要查询或修改的最后元素的后一个元素。 确定两个元素集合是否是相…

手写Spring8(Aware感知容器变化)

文章目录目标设计思想项目结构一、实现1、定义标记接口2、容器感知类2.1、BeanFactoryAware2.2、BeanFactoryAware2.3、BeanNameAware2.4、ApplicationContextAware3、包装处理器(ApplicationContextAwareProcessor)4、注册 BeanPostProcessor5、感知调用操作二、测试1、事先准…

(附源码)springboo计算机专业大学生就业指南网 毕业设计 061355

计算机专业大学生就业指南网 摘 要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对学生就业管理等…

Premiere Pro 快捷键大全(2023版)

说明&#xff1a;为避免篇幅过大&#xff0c;本文快捷键是基于 Windows 系统 Pr 2023 版本的。Mac系统下的快捷键可按以下方式进行对应&#xff1a;Ctrl→Command&#xff0c;Alt→Option。有不能对应的&#xff0c;本文会给出说明。◆ ◆ ◆媒体相关新建序列&#xff1a;Ctrl …

创建Series()对象--pandas

1. 函数功能 产生带有标签(索引)的一维数组&#xff0c;Series对象中的数据可以是任意类型&#xff08;整型、字符串、浮点型、python对象等&#xff09; 2. 函数语法 pandas.Series(dataNone, indexNone, dtypeNone, nameNone, copyFalse, fastpathFalse)3. 函数参数与示例…

猿如意初体验!赞一个。

目录 功能一&#xff1a;chatGPT 功能二、对 “效率工具”的试体验&#xff01; 功能三&#xff1a;教程文档 最后总结 猿如意传送门猿如意下载地址&#xff1a;猿如意-程序员的如意兵器,工具代码,一搜就有 猿如意使用了几次了&#xff0c;今天来想分享一下我对于猿如意的…

工程复现 -- SiamMOT

工程复现 – SiamMOT 先赞后看&#xff0c;养成好习惯&#xff0c;感谢您的理解与支持&#xff01; 参考&#xff1a; 1. siam-mot源码地址 2. SiamMOT 论文地址 3. SiamMOT 论文解析 简单介绍 SiamMOT&#xff08;SiamMOT: Siamese Multi-Object Tracking&#xff09;是…

微服务框架 SpringCloud微服务架构 服务异步通讯 53 MQ 集群 53.5 仲裁队列【SpringAMQP】

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 服务异步通讯 文章目录微服务框架服务异步通讯53 MQ 集群53.5 仲裁队列【SpringAMQP】53.5.1 仲裁队列53 MQ 集群 53.5 仲裁队列【SpringAM…

Jvm学习笔记

&#x1f3b6; 文章简介&#xff1a;Jvm学习笔记 &#x1f4a1; 创作目的&#xff1a;Jvm学习笔记 ☀️ 今日天气&#xff1a;天气很好 &#x1f4dd; 每日一言&#xff1a;乾坤琉璃色&#xff0c;碧宇凝清光。 文章目录类文件结构详解一 概述二 Class 文件结构总结2.1 魔数&am…