SpringCloudRPC远程调用核心原理:Feign弹性RPC客户端的重要组件

1495 篇文章 10 订阅
1493 篇文章 14 订阅

Feign弹性RPC客户端的重要组件

在微服务启动时,Feign会进行包扫描,对加@FeignClient注解的RPC接口创建远程接口的本地JDK动态代理实例。之后这些本地Proxy动态代理实例会注入Spring IOC容器中。当远程接口的方法被调用时,由Proxy动态代理实例负责完成真正的远程访问并返回结果。

演示用例说明

为了演示Feign的远程调用动态代理类,接下来的演示用例,将从uaa-provider服务实例向demo-provider服务实例发起RPC远程调用,调用流程如图3-10所示。

图3-10 从uaa-provider实例向demo-provider实例发起远程调用

uaa-provider服务中的DemoRPCController类的代码如下:

package com.crazymaker.springcloud.user.info.controller;
//省略import
@RestController
@RequestMapping("/api/call/demo/")
@Api(tags = "演示demo-provider远程调用")
public class DemoRPCController
{
 //注入 @FeignClient注解所配置的demo-provider远程客户端动态代理实例
 @Resource
 DemoClient demoClient;
 @GetMapping("/hello/v1")
 @ApiOperation(value = "hello远程调用")
 public RestOut<JSONObject> remoteHello()
 {
 /**
 *调用demo-provider的REST接口api/demo/hello/v1
 */
 RestOut<JSONObject> result = demoClient.hello(); JSONObject data = new JSONObject();
 data.put("demo-data", result);
 return RestOut.success(data).setRespMsg("操作成功");
 }
 @GetMapping("/echo/{word}/v1")
 @ApiOperation(value = "echo远程调用")
 public RestOut<JSONObject> remoteEcho(
 @PathVariable(value = "word") String word)
 {
 /**
 *调用demo-provider的REST接口api/demo/echo/{0}/v1
 */
 RestOut<JSONObject> result = demoClient.echo(word);
 JSONObject data = new JSONObject();
 data.put("demo-data", result);
 return RestOut.success(data).setRespMsg("操作成功");
 }
}

启动uaa-provider服务后,访问其swagger-ui接口,可以看到新增两个demo-provider实例进行RPC调用的REST接口,如图3-11所示。

图3-11 uaa-provider新增的对demo-provider实例进行RPC调用的两个 接口

本章后面的Feign动态代理RPC客户端类的知识都是基于此演示用例来进行介绍的,特殊情况下还需要在uaa-provider的方法执行时进行单步调试,以查看Feign在执行过程中的相关变量和属性的值。当然,在演示uaa-provider之前需要启动demo-provider服务。

基于以上演示用例,下面开始梳理Feign中涉及RPC远程调用的几个重要组件。

Feign的动态代理RPC客户端实例

由于uaa-provider服务需要对demo-provider服务进行Feign RPC调用,因此uaa-provider需要依赖DemoClient远程调用接口,该接口的代码大家都非常熟悉了,如下所示:

package com.crazymaker.springcloud.demo.contract.client;
//省略import
@FeignClient(
 value = "seckill-provider", path = "/api/demo/",
 fallback = DemoDefaultFallback.class)
public interface DemoClient
{
 /**
 *远程调用接口的方法
 *调用demo-provider的REST接口api/demo/hello/v1
 *REST接口功能:返回hello world
 *@return JSON响应实例
 */
 @GetMapping("/hello/v1")
 RestOut<JSONObject> hello();
 /**
 *远程调用接口的方法
 *调用demo-provider的REST接口api/demo/echo/{0}/v1
 *REST接口功能:回显输入的信息
 *@return echo回显消息JSON响应实例
 */
 @RequestMapping(value = "/echo/{word}/v1",
 method = RequestMethod.GET)
 RestOut<JSONObject> echo(
 @PathVariable(value = "word") String word);
}

注意,DemoClient远程调用接口加有@FeignClient注解,Feign在启动时会为带有@FeignClient注解的接口创建一个动态代理RPC客户端实例,并注册到Spring IOC容器,如图3-12所示。

图3-12 远程调用接口DemoClient的动态代理RPC客户端实例

DemoClient的本地JDK动态代理实例的创建过程比较复杂,稍后将作为重点介绍。先来看另外两个重要的Feign逻辑组件——调用处理器和方法处理器。

 Feign的调用处理器InvocationHandler

大家知道,通过JDK Proxy生成动态代理类的核心步骤就是定制一个调用处理器。调用处理器实现类需要实现JDK中位于java.lang.reflect包中的InvocationHandler调用处理器接口,并且实现该接口的invoke(...)抽象方法。

Feign提供了一个默认的调用处理器,名为FeignInvocationHandler类,该类完成基本的调用处理逻辑,处于feign-core核心JAR包中。当然,Feign的调用处理器可以进行替换,如果Feign是与Hystrix结合使用的,就会被替换成HystrixInvocationHandler调用处理器类,而该类处于feign-hystrix的JAR包中。

以上两个Feign调用处理器都实现了JDK的InvocationHandler接口,如图3-13所示。

图3-13 两个Feign的InvocationHandler调用处理器示意图

默认的调用处理器FeignInvocationHandler是一个相对简单的类,有一个非常重要的Map类型成员dispatch映射,保存着RPC方法反射实例到Feign的方法处理器MethodHandler实例的映射。

在演示示例中,DemoClient接口的JDK动态代理实现类的调用处理器FeignInvocationHandler的某个实例的dispatch成员的内存结构图如图3-14所示。

图3-14 一个运行时FeignInvocationHandler调用处理器实例的 dispatch成员的内存结构图

DemoClient的动态代理实例的调用处理器FeignInvocationHandler的dispatch成员映射中有两个键-值对(Key-Value Pair):一个键-值对缓存的是hello方法的方法处理器实例;另一个键-值对缓存的是echo方法的方法处理器实例。

在处理远程方法调用时,调用处理器FeignInvocationHandler会根据被调远程方法的Java反射实例在dispatch映射中找到对应的MethodHandler方法处理器,然后交给MethodHandler去完成实际的HTTP请求和结果的处理。

Feign的调用处理器FeignInvocationHandler的关键源码节选如下:

package feign;
//省略import
public class ReflectiveFeign extends Feign {
 ...
 //内部类:默认的Feign调用处理器FeignInvocationHandler
 static class FeignInvocationHandler implements InvocationHandler {
 private final Target target;
//RPC方法反射实例和方法处理器的映射
 private final Map<Method, MethodHandler> dispatch;
 //构造函数
 FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
 this.target = checkNotNull(target, "target");
 this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}
 //默认Feign调用的处理
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 ...
 //首先,根据方法反射实例从dispatch中取得MethodHandler方法处理器实例
 //然后,调用方法处理器的invoke(...) 方法
 return dispatch.get(method).invoke(args);
 }
 ...
 }

以上源码很简单,重点在于invoke(...)方法,虽然核心代码只有一行,但有两个功能:

(1)根据被调RPC方法的Java反射实例在dispatch映射中找到对应的MethodHandler方法处理器。

(2)调用MethodHandler方法处理器的invoke(...)方法完成实际的RPC远程调用,包括HTTP请求的发送和响应的解码。

Feign的方法处理器MethodHandler

Feign的方法处理器MethodHandler接口和JDK动态代理机制中的InvocationHandler调用处理器接口没有任何的继承和实现关系。

Feign的MethodHandler接口是Feign自定义接口,是一个非常简单的接口,只有一个invoke(...)方法,并且定义在InvocationHandlerFactory工厂接口的内部,MethodHandler接口源码如下:

//定义在InvocationHandlerFactory接口中
public interface InvocationHandlerFactory {
 ...
 //方法处理器接口,仅仅拥有一个invoke(...)方法
 interface MethodHandler {
 //完成远程URL请求
 Object invoke(Object[] argv) throws Throwable;
 }
...
}

MethodHandler的invoke(...)方法的主要目标是完成实际远程URL请求,然后返回解码后的远程URL的响应结果。Feign内置提供了SynchronousMethodHandler和DefaultMethodHandler两种方法处理器的实现类,如图3-15所示

图3-15 Feign的MethodHandler方法处理器及其实现类

内置的SynchronousMethodHandler同步方法处理实现类是Feign的一个重要类,提供了基本的远程URL的同步请求响应处理。

SynchronousMethodHandler方法处理器的源码如下:

package feign;
//省略import
final class SynchronousMethodHandler implements MethodHandler {
 ...
 private static final long MAX_RESPONSE_BUFFER_SIZE = 8192L;
 private final MethodMetadata metadata; //RPC远程调用方法的元数据
 private final Target<?> target; //RPC远程调用Java接口的元数据
 private final Client client; //Feign客户端实例:执行REST请求和处理响应
 private final Retryer retryer;
 private final List<RequestInterceptor> requestInterceptors; //请求拦截器
 ...
 private final Decoder decoder; //结果解码器
 private final ErrorDecoder errorDecoder;
 private final boolean decode404; //是否反编码404
 private final boolean closeAfterDecode;
 //执行Handler的处理
 public Object invoke(Object[] argv) throws Throwable {
 RequestTemplate requestTemplate = this.buildTemplateFromArgs
.create(argv);
 ... while(true) {
 try {
 return this.executeAndDecode(requestTemplate); //执行REST请求和处理响应
 } catch (RetryableException var5) {
 ...
 }
 }
 }
 //执行RPC远程调用,然后解码结果
 Object executeAndDecode(RequestTemplate template) throws Throwable {
 Request request = this.targetRequest(template);
 long start = System.nanoTime();
 Response response;
 try {
 response = this.client.execute(request, this.options);
 response.toBuilder().request(request).build();
 }
 }
}

SynchronousMethodHandler的invoke(...)方法首先生成请求模板requestTemplate实例,然后调用内部成员方法executeAndDecode()执行RPC远程调用。

SynchronousMethodHandler的成员方法executeAndDecode()执行流程如下:

(1)通过请求模板requestTemplate实例生成目标request请求实例,主要完成请求的URL、请求参数、请求头等内容的封装。

(2)通过client(Feign客户端)成员发起真正的RPC远程调用。

(3)获取response响应,并进行结果解码。

SynchronousMethodHandler的主要成员如下:

(1)Target<?>target:RPC远程调用Java接口的元数据,保存了RPC接口的类名称、服务名称等信息,换句话说,远程调用Java接口的@FeignClient注解中配置的主要属性值都保存在target实例中。

(2)MethodMetadata metadata:RPC方法的元数据,该元数据首先保存了RPC方法的配置键,格式为“接口名#方法名(形参表)”;其次保存了RPC方法的请求模板(包括URL、请求方法等);再次保存了RPC方法的returnType返回类型;另外还保存了RPC方法的一些其他的属性。

(3)Client client:Feign客户端实例是真正执行RPC请求和处理响应的组件,默认实现类为Client.Default,通过JDK的基础连接类HttpURLConnection发起HTTP请求。Feign客户端有多种实现类,比如封装了Apache HttpClient组件的
feign.httpclient.HttpClient客户端实现类,稍后详细介绍。

(4)List<RequestInterceptor>requestInterceptors:每个请求执行前加入拦截器的逻辑。

(5)Decoder decoder:HTTP响应的解码器。

同步方法处理器SynchronousMethodHandler的属性较多,这里不一一介绍了。其内部有一个Factory工厂类,负责其实例的创建。创建一个SynchronousMethodHandler实例的源码如下:

package feign;
...
//同步方法调用器
final class SynchronousMethodHandler implements MethodHandler {
 ...
 //同步方法调用器的创建工厂
 static class Factory {
 private final Client client; //Feign客户端:负责RPC请求和处理响应
 private final Retryer retryer;
 private final List<RequestInterceptor> requestInterceptors; //请求拦截器
 private final Logger logger;
 private final Level logLevel;
 private final boolean decode404; //是否解码404错误响应
 private final boolean closeAfterDecode;
//省略Factory创建工厂的全参构造器
 //工厂的默认创建方法:创建一个方法调用器
 public MethodHandler create(Target<?> target, MethodMetadata md,
 feign.RequestTemplate.Factory buildTemplateFromArgs,
 Options options, Decoder decoder, ErrorDecoder errorDecoder) {
 //返回一个新的同步方法调用器
 return new SynchronousMethodHandler(target, this.client,
 this.retryer, this.requestInterceptors, this.logger, this.logLevel, md,
 buildTemplateFromArgs, options, decoder,
 errorDecoder, this.decode404, this.closeAfterDecode);
 }
 }
}

 Feign的客户端组件

客户端组件是Feign中一个非常重要的组件,负责最终的HTTP(包括REST)请求的执行。它的核心逻辑:发送Request请求到服务器,在接收到Response响应后进行解码,并返回结果。

feign.Client接口是代表客户端的顶层接口,只有一个抽象方法,源码如下:

package feign;
/**客户端接口
 *Submits HTTP {@link Request requests}.
*Implementations are expected to be thread-safe.
 */
public interface Client {
 //提交HTTP请求,并且接收response响应后进行解码
 Response execute(Request request, Options options) throws IOException;
}

不同的feign.Client客户端实现类其内部提交HTTP请求的技术是不同的。常用的Feign客户端实现类如下:

(1)Client.Default类:默认的实现类,使用JDK的HttpURLConnnection类提交HTTP请求。

(2)ApacheHttpClient类:该客户端类在内部使用ApacheHttpClient开源组件提交HTTP请求。

(3)OkHttpClient类:该客户端类在内部使用OkHttp3开源组件提交HTTP请求。

(4)LoadBalancerFeignClient类:内部使用Ribbon负载均衡技术完成HTTP请求处理。Feign客户端组件的UML图如图3-16所示。

图3-16 Feign客户端组件的UML图

下面对以上4个常用的客户端实现类进行简要介绍。

1.Client.Default默认实现类

作为默认的Client接口的实现类,Client.Default内部使用JDK自带的HttpURLConnnection类提交HTTP请求。

Client.Default默认实现类的方法如图3-17所示。

图3-17 Client.Default默认实现类的方法

在JDK 1.8中,虽然HttpURLConnnection底层使用了非常简单的HTTP连接池技术,但是其HTTP连接的复用能力实际上是非常弱的,所以其性能也比较低,不建议在生产环境中使用。

2.ApacheHttpClient实现类

ApacheHttpClient客户端类的内部使用Apache HttpClient开源组件提交HTTP请求。

和JDK自带的HttpURLConnnection连接类比,Apache HttpClient更加易用和灵活,它不仅使客户端发送HTTP请求变得容易,而且方便开发人员测试接口,既可以提高开发的效率,又可以提高代码的健壮性。从性能的角度而言,ApacheHttpClient带有连接池的功能,具备优秀的HTTP连接的复用能力。客户端实现类ApacheHttpClient处于feign-httpclient独立JAR包中,如果使用,还需引入配套版本的JAR包依赖。疯狂创客圈的脚手架crazy-springcloud使用了ApacheHttpClient客户端,在各Provider微服务提供者模块中加入了feign-httpclient和httpclient两个组件的依赖坐标,具体如下:

<dependency>
 <groupId>io.github.openfeign</groupId>
 <artifactId>feign-httpclient</artifactId>
 <version>${feign-httpclient.version}</version>
</dependency>
<!--https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
 <groupId>org.apache.httpcomponents</groupId>
 <artifactId>httpclient</artifactId>
 <version>${httpclient.version}</version>
</dependency>

另外,在配置文件中将配置项feign.httpclient.enabled的值设置为true,表示需要启用ApacheHttpClient。

3.OkHttpClient实现类

OkHttpClient客户端内部使用了开源组件OkHttp3提交HTTP请求。

OkHttp3组件是Square公司开发的,用于替代HttpUrlConnection和ApacheHttpClient的高性能HTTP组件。OkHttp3较好地支持SPDY协议(SPDY是Google开发的基于TCP的传输层协议,用以最小化网络延迟、提升网络速度、优化用户的网络使用体验),并且从Android 4.4开始,Google已经开始将Android源码中的JDK连接类HttpURLConnection使用OkHttp进行了替换。

4.LoadBalancerFeignClient负载均衡客户端实现类

该客户端类处于Feign核心JAR包中,在内部使用Ribbon开源组件实现多个Provider实例之间的负载均衡。它的内部有一个封装的delegate被委托客户端成员,该成员才是最终的HTTP请求提交者。Ribbon负载均衡组件计算出合适的服务端Provider实例之后,由delegate被委托客户端完成到Provider服务端之间的HTTP请求。

LoadBalancerFeignClient封装的delegate被委托客户端的类型可以是Client.Default默认客户端,也可以是ApacheHttpClient客户端类或OkHttpClient客户端类,或者其他的定制类。

LoadBalancerFeignClient负载均衡客户端实现类的UML类图如图3-18所示。

图3-18 LoadBalancerFeignClient负载均衡客户端实现类

除了以上4个feign.Client客户端实现类外,还可以定制自己的feign.Client实现类。

本文给大家讲解的内容是SpringCloudRPC远程调用核心原理:Feign弹性RPC客户端的重要组件

  1. 下篇文章给大家讲解的是SpringCloudRPC远程调用核心原理:Feign的RPC动态代理实例的创建流程;
  2. 觉得文章不错的朋友可以转发此文关注小编;
  3. 感谢大家的支持!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值