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远程客户端动态代理实例@ResourceDemoClient 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调用处理器FeignInvocationHandlerstatic 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调用的处理@Overridepublic 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; //是否反编码404private 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. 感谢大家的支持!

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

  1. 远程调用:远程过程调用(RPC)和远程方法调用(RMI)

    远程调用包括远程过程调用(RPC)和远程方法调用(RMI) 1.请求-应答协议 请求-应答协议描述了一个基于消息传递的范型,支持消息双向传输. 涉及三个通信原语 (1)doOperation:向指定的 ...

  2. 基于 Hessian 轻量级远程调用的原理及示例

    1 简介 Hessian 是 Caucho 公司开发的一种基于二进制 RPC 协议(Remote Procedure Call protocol)的轻量级远程调用框架,其使用简单的方法提供了 RMI ...

  3. 远程调用 Spring Cloud Feign

    一. Feign简介 Feign [feɪn] 译文 伪装.Feign是一个声明式WebService客户端.使用Feign能让编写WebService客户端更加简单,它的使用方法是定义一个接口,然后 ...

  4. RPC远程调用,go语言实现RPC小Demo

    RPC(远程过程调用) :它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议. 采用客户机/服务器模式.请求程序是一个客户机.而服务服务提供程序就是一个服务器. **过程:** ...

  5. 实现远程调用_远程过程调用(RPC)是怎么实现的?

    1 RPC简介 RPC(Remote Procedure Call)-远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议.RPC协议假定某些传输协议的存在,如TC ...

  6. 分布式通信:远程调用

    分布式通信:远程调用 前言 什么是远程调用? 远程调用的原理及应用 RPC 的原理及应用 RMI 的原理及应用 RPC 与 RMI 对比分析 知识扩展:远程过程调用存在同步和异步吗? 总结 前言 分布 ...

  7. 《Spring技术内幕》学习笔记17——Spring HTTP调用器实现远程调用

    1.Spring中,HTTPInvoker(HTTP调用器)是通过基于HTTP协议的分布式远程调用解决方案,和java RMI一样,HTTP调用器也需要使用java的对象序列化机制完成客户端和服务器端 ...

  8. SpringCloud 中 Feign 核心原理,简单易懂!

    目录 SpringCloud 中 Feign 核心原理 Feign远程调用的基本流程 Feign 远程调用的重要组件 Feigh 远程调用的执行流程 SpringCloud 中 Feign 核心原理 ...

  9. Rpc远程调用框架的设计与实现(1)

    Rpc远程调用框架的设计与实现 1   Rpc远程调用框架设计概述 1.1  研究背景 1.1.1传统的Web开发方式 在传统的Web应用程序中,一般都是采取请求→刷新→显示的模式.即每当用户通过单击 ...

  10. 提交响应后无法调用sendredirect_微服务的那些事(三),微服务的远程调用方式。RPC和HTTP...

    2.远程调用方式 无论是微服务还是SOA,都面临着服务间的远程调用.那么服务间的远程调用方式有哪些呢? 常见的远程调用方式有以下几种: RPC:Remote Produce Call远程过程调用,类似 ...

最新文章

  1. 【网络爬虫】BeautfulSoup爬百度百科(真の能看懂~!)
  2. Hbase API中常用类介绍和使用
  3. hdu 5230(整数划分,dp)
  4. 关于Bitmap中的inBitmap变量的学习与使用
  5. 开关管三极管和MOS管的选择
  6. 用 Python 手写机器学习最简单的 KNN 算法
  7. 私有服务器虚拟化软件市场排名,操作系统、数据库和虚拟化软件2017年市场格局分析...
  8. 【软件工程】北邮国际学院大三下期末复习
  9. 尼古拉·特斯拉16句经典名言
  10. MySQL条件查询IN和NOT IN左右两侧包含NULL值的处理方式
  11. pytorch搭建深度学习网络
  12. 飞机的纵•横向运动简化数学模型及控制系统设计
  13. VS code C语言输出位数
  14. 【历史上的今天】11 月 25 日:P2P 鼻祖 Napster 被收购;机械计算器之父诞生;高春辉的个人网站
  15. 及时止损,及时止损,及时止损
  16. Oracle11G数据泵expdp/impdp使用并行与压缩技术备份与恢复
  17. 解读银保监“个人信息保护专项整治”,强监管下金融业个人信息安全保护如何“守”?|特邀专栏
  18. android 精灵图的使用方法,css sprites(精灵图)如何使用?
  19. 更改用友单据打印时行高
  20. html垂直居中的方法

热门文章

  1. 机器认知、人机交互、边缘计算……在这里,他们谈论了关于AI的关键议题...
  2. RocketMQTemplate发送带tags的消息
  3. 服务器3389信息,服务器3389远程记录查看
  4. 深度学习(PyTorch)——shape、view、reshape用法及其区别
  5. android edittext怎样获取输入的内容,如何获取edittext中输入的内容?
  6. 作为一名程序员,我都收集了哪些好玩的生成器?
  7. 【TWVRP】蚁群算法求解带时间窗的车辆路径规划问题【含Matlab源码 921期】
  8. “决策树”——数据挖掘、数据分析
  9. 写了100条测试用例,被正经执行的只有50条?
  10. cl.clus pw index.php,Application Essay 写作 第五课 Introductions and Conclus