SpringCloud远程服务调用三种方式及原理

 更新时间:2022年12月19日 14:37:42   作者:nimo10050  
本文给大家介绍SpringCloud远程服务调用实战笔记,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

一个简单的微服务架构图

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

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

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

调用远程服务的三种方式

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

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

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

1、基于 RestTemplate 和 @LoadBalanced 注解

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

添加到 spring 容器

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

使用前注入依赖

@Autowired
private RestTemplate restTemplate;

常用 API

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

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

  • 根据服务名拿到服务实例的信息
  • 负载均衡算法

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

在原有基础上加上 @LoadBalanced 注解

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

调用接口时,传入服务名称

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

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

2、基于DiscoveryClient

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

注入依赖

@Autowired
private DiscoveryClient discoveryClient;

获取注册中心服务实例列表

List<ServiceInstance> instances = discoveryClient.getInstances("user-service");

选取一个实例的地址信息, 发送请求

3、基于 Feign 的声明式调用

在启动类上加对应的注解.

@EnableFeignClients

声明接口

@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 这个类做检索, 这里不再详细阐述.

到此这篇关于SpringCloud远程服务调用三种方式及原理的文章就介绍到这了,更多相关SpringCloud服务调用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 带你快速搞定java多线程(4)

    带你快速搞定java多线程(4)

    这篇文章主要介绍了java多线程编程实例,分享了几则多线程的实例代码,具有一定参考价值,加深多线程编程的理解还是很有帮助的,需要的朋友可以参考下
    2021-07-07
  • Java后台线程操作示例【守护线程】

    Java后台线程操作示例【守护线程】

    这篇文章主要介绍了Java后台线程操作,结合实例形式分析了java守护线程相关原理、操作技巧与使用注意事项,需要的朋友可以参考下
    2019-09-09
  • Java创建型设计模式之抽象工厂模式(Abstract Factory)

    Java创建型设计模式之抽象工厂模式(Abstract Factory)

    当系统所提供的工厂所需生产的具体产品并不是一个简单的对象,而是多个位于不同产品等级结构中属于不同类型的具体产品时需要使用抽象工厂模式,抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形态
    2022-09-09
  • spring profile 多环境配置管理详解

    spring profile 多环境配置管理详解

    这篇文章主要介绍了 spring profile 多环境配置管理详解的相关资料,需要的朋友可以参考下
    2017-01-01
  • Java与Unix时间戳的相互转换详解

    Java与Unix时间戳的相互转换详解

    这篇文章主要为大家详细介绍了Java与Unix时间戳的相互转换,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-12-12
  • java中的快捷键小结

    java中的快捷键小结

    以下是myeclipse中的所有快捷键列表
    2013-03-03
  • 使用Jackson反序列化遇到的问题及解决

    使用Jackson反序列化遇到的问题及解决

    这篇文章主要介绍了使用Jackson反序列化遇到的问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • JAVA中static方法的用法实例详解

    JAVA中static方法的用法实例详解

    这篇文章主要介绍了JAVA中static方法的用法,结合实例形式较为详细的分析了Java中static方法的功能、使用技巧与相关注意事项,需要的朋友可以参考下
    2015-12-12
  • ChatGPT介绍及Java API调用

    ChatGPT介绍及Java API调用

    本文主要介绍了ChatGPT介绍及Java API调用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-03-03
  • 四个Java必须知道的负载均衡算法分享

    四个Java必须知道的负载均衡算法分享

    我们在设计系统的时候,为了系统的高扩展性,会创建无状态的系统。但是,要使系统具有更好的可扩展性,除了无状态设计之外,还要考虑采用什么负载均衡算法,本文就带领大家认识以下常见的4种负载均衡算法
    2023-01-01

最新评论