SpringCloudRPC远程调用核心原理:Feign弹性RPC客户端的重要组件
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客户端的重要组件
- 下篇文章给大家讲解的是SpringCloudRPC远程调用核心原理:Feign的RPC动态代理实例的创建流程;
- 觉得文章不错的朋友可以转发此文关注小编;
- 感谢大家的支持!
SpringCloudRPC远程调用核心原理:Feign弹性RPC客户端的重要组件相关推荐
- 远程调用:远程过程调用(RPC)和远程方法调用(RMI)
远程调用包括远程过程调用(RPC)和远程方法调用(RMI) 1.请求-应答协议 请求-应答协议描述了一个基于消息传递的范型,支持消息双向传输. 涉及三个通信原语 (1)doOperation:向指定的 ...
- 基于 Hessian 轻量级远程调用的原理及示例
1 简介 Hessian 是 Caucho 公司开发的一种基于二进制 RPC 协议(Remote Procedure Call protocol)的轻量级远程调用框架,其使用简单的方法提供了 RMI ...
- 远程调用 Spring Cloud Feign
一. Feign简介 Feign [feɪn] 译文 伪装.Feign是一个声明式WebService客户端.使用Feign能让编写WebService客户端更加简单,它的使用方法是定义一个接口,然后 ...
- RPC远程调用,go语言实现RPC小Demo
RPC(远程过程调用) :它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议. 采用客户机/服务器模式.请求程序是一个客户机.而服务服务提供程序就是一个服务器. **过程:** ...
- 实现远程调用_远程过程调用(RPC)是怎么实现的?
1 RPC简介 RPC(Remote Procedure Call)-远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议.RPC协议假定某些传输协议的存在,如TC ...
- 分布式通信:远程调用
分布式通信:远程调用 前言 什么是远程调用? 远程调用的原理及应用 RPC 的原理及应用 RMI 的原理及应用 RPC 与 RMI 对比分析 知识扩展:远程过程调用存在同步和异步吗? 总结 前言 分布 ...
- 《Spring技术内幕》学习笔记17——Spring HTTP调用器实现远程调用
1.Spring中,HTTPInvoker(HTTP调用器)是通过基于HTTP协议的分布式远程调用解决方案,和java RMI一样,HTTP调用器也需要使用java的对象序列化机制完成客户端和服务器端 ...
- SpringCloud 中 Feign 核心原理,简单易懂!
目录 SpringCloud 中 Feign 核心原理 Feign远程调用的基本流程 Feign 远程调用的重要组件 Feigh 远程调用的执行流程 SpringCloud 中 Feign 核心原理 ...
- Rpc远程调用框架的设计与实现(1)
Rpc远程调用框架的设计与实现 1 Rpc远程调用框架设计概述 1.1 研究背景 1.1.1传统的Web开发方式 在传统的Web应用程序中,一般都是采取请求→刷新→显示的模式.即每当用户通过单击 ...
- 提交响应后无法调用sendredirect_微服务的那些事(三),微服务的远程调用方式。RPC和HTTP...
2.远程调用方式 无论是微服务还是SOA,都面临着服务间的远程调用.那么服务间的远程调用方式有哪些呢? 常见的远程调用方式有以下几种: RPC:Remote Produce Call远程过程调用,类似 ...
最新文章
- 【网络爬虫】BeautfulSoup爬百度百科(真の能看懂~!)
- Hbase API中常用类介绍和使用
- hdu 5230(整数划分,dp)
- 关于Bitmap中的inBitmap变量的学习与使用
- 开关管三极管和MOS管的选择
- 用 Python 手写机器学习最简单的 KNN 算法
- 私有服务器虚拟化软件市场排名,操作系统、数据库和虚拟化软件2017年市场格局分析...
- 【软件工程】北邮国际学院大三下期末复习
- 尼古拉·特斯拉16句经典名言
- MySQL条件查询IN和NOT IN左右两侧包含NULL值的处理方式
- pytorch搭建深度学习网络
- 飞机的纵•横向运动简化数学模型及控制系统设计
- VS code C语言输出位数
- 【历史上的今天】11 月 25 日:P2P 鼻祖 Napster 被收购;机械计算器之父诞生;高春辉的个人网站
- 及时止损,及时止损,及时止损
- Oracle11G数据泵expdp/impdp使用并行与压缩技术备份与恢复
- 解读银保监“个人信息保护专项整治”,强监管下金融业个人信息安全保护如何“守”?|特邀专栏
- android 精灵图的使用方法,css sprites(精灵图)如何使用?
- 更改用友单据打印时行高
- html垂直居中的方法
热门文章
- 机器认知、人机交互、边缘计算……在这里,他们谈论了关于AI的关键议题...
- RocketMQTemplate发送带tags的消息
- 服务器3389信息,服务器3389远程记录查看
- 深度学习(PyTorch)——shape、view、reshape用法及其区别
- android edittext怎样获取输入的内容,如何获取edittext中输入的内容?
- 作为一名程序员,我都收集了哪些好玩的生成器?
- 【TWVRP】蚁群算法求解带时间窗的车辆路径规划问题【含Matlab源码 921期】
- “决策树”——数据挖掘、数据分析
- 写了100条测试用例,被正经执行的只有50条?
- cl.clus pw index.php,Application Essay 写作 第五课 Introductions and Conclus