由于Feign中生成RPC接口JDK动态代理实例涉及的InvocationHandler调用处理器有多种,导致Feign远程调用的执行流程稍微有所区别,但是远程调用执行流程的主要步骤是一致的。这里主要介绍与两类InvocationHandler调用处理器相关的RPC执行流程:
(1)与默认的调用处理器FeignInvocationHandler相关的RPC执行流程。
(2)与Hystrix调用处理器HystrixInvocationHandler相关的RPC执行流程。
还是以uaa-provider启动过程中的DemoClient接口的动态代理实例的执行过程为例演示和分析远程调用的执行流程。
FeignInvocationHandler是默认的调用处理器,如果进行特殊的配置,那么Feign将默认使用此调用处理器。
结合uaa-provider服务中DemoClient的动态代理实例的hello()方法远程调用执行过程,这里详细介绍与FeignInvocationHandler相关的远程调用执行流程,如图3-25所示。
图3-25 与FeignInvocationHandler相关的远程调用执行流程
整体的远程调用执行流程大致分为4步,具体如下:
(1)通过Spring IOC容器实例完成动态代理实例的装配。
前文讲到,Feign在启动时会为加上了@FeignClient注解的所有远程接口(包括DemoClient接口)创建一个FactoryBean工厂实例,并注册到Spring IOC容器。然后在uaa-provider的DemoRPCController控制层类中,通过@Resource注解从Spring IOC容器找到FactoryBean工厂实例,通过其getObject()方法获取到动态代理实例,装配给DemoRPCController实例的成员变量demoClient。
在需要进行hello()远程调用时,直接通过demoClient成员变量调用JDK动态代理实例的hello()方法。
(2)执行InvocationHandler调用处理器的invoke(...)方法。
前面讲到,JDK动态代理实例的方法调用过程是通过委托给InvocationHandler调用处理器完成的,故在调用demoClient的hello()方法时,会调用到它的调用处理器FeignInvocationHandler实例的invoke(...)方法。
大家知道,FeignInvocationHandler实例内部保持了一个远程调用方法反射实例和方法处理器的dispatch映射。FeignInvocationHandle在它的invoke(...)方法中会根据hello()方法的Java反射实例在dispatch映射对象中找到对应的MethodHandler方法处理器,然后由后者完成实际的HTTP请求和结果的处理。
(3)执行MethodHandler方法处理器的invoke(...)方法。
通过前面关于MethodHandler方法处理器的组件介绍,大家都知道,feign默认的方法处理器为SynchronousMethodHandler同步调用处理器,它的invoke(...)方法主要通过内部feign。Client类型的client成员实例完成远程URL请求执行和获取远程结果。
feign.Client客户端有多种类型,不同的类型完成URL请求处理的具体方式不同。(4)通过feign.Client客户端成员完成远程URL请求执行和获取远程结果。
如果MethodHandler方法处理器client成员实例是默认的feign.Client.Default实现类,就通过JDK自带的HttpURLConnnection类完成远程URL请求执行和获取远程结果。
如果MethodHandler方法处理器实例的client客户端是ApacheHttpClient客户端实现类,就使用ApacheHttpClient开源组件完成远程URL请求执行和获取远程结果。
如果MethodHandler方法处理器实例的client客户端是LoadBalancerFeignClient负载均衡客户端实现类,就使用Ribbon结算出最佳的Provider节点,然后由内部的delegate委托客户端成员去请求Provider服务,完成URL请求处理。
以上4步基本上就是Spring Cloud中的Feign远程调用的执行流程。
然而,默认的基于FeignInvocationHandler调用处理器的执行流程在运行机制和调用性能上都满足不了生产环境的要求,大致原因有以下两点:
(1)在远程调用过程中没有异常的熔断监测和恢复机制。
(2)没有用到高性能的HTTP连接池技术。
接下来将为大家介绍一种结合Hystrix进行RPC保护的远程调用处理流程。在该流程中所使用的InvocationHandler调用处理器叫作HystrixInvocationHandler调用处理器。
这里作为铺垫,首先为大家介绍HystrixInvocationHandler调用处理器本身的具体实现。
HystrixInvocationHandler调用处理器类位于feign.hystrix包中,其字节码文件不是处于feign核心包feign-core-*.jar中,而是在扩展包feignhystrix-*.jar中。这里的*表示的是与Spring Cloud版本配套的版本号,当Spring Cloud的版本为Finchley.RELEASE时,feign-core和feign-hystrix两个JAR包的版本号都为9.5.1。
HystrixInvocationHandler是具备RPC保护能力的调用处理器,它实现了InvocationHandler接口,对接口的invoke(...)抽象方法的实现如下:
package feign.hystrix;
//省略import
final class HystrixInvocationHandler implements InvocationHandler {
...
//... Map映射:Key为RPC方法的反射实例,value为方法处理器
private final Map dispatch;
...
public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
//创建一个HystrixCommand命令,对同步方法调用器进行封装
HystrixCommand
HystrixInvocationHandler调用处理器与默认调用处理器FeignInvocationHandler有一个共同点:都有一个非常重要的Map类型成员dispatch映射,保存着RPC方法反射实例到MethodHandler方法处理器的映射。
在源码中,HystrixInvocationHandler的invoke(...)方法会创建hystrixCommand命令实例,对从dispatch获取的SynchronousMethodHandler实例进行封装,然后对RPC方法实例method进行判断,判断是直接返回hystrixCommand命令实例,还是立即执行其execute()方法。默认情况下,都是立即执行它的execute()方法。
HystrixCommand具备熔断、隔离、回退等能力,如果它的run()方法执行发生异常,就会执行getFallback()失败回调方法,这一点后面会详细介绍。
回到uaa-provider服务中DemoClient动态代理实例的hello()方法的具体执行过程,在执行命令处理器hystrixCommand实例的run()方法时,步骤如下:
(1)根据RPC方法DemoClient.hello()的反射实例在dispatch映射对象中找到对应的方法处理器MethodHandler实例。
(2)调用MethodHandler方法处理器的invoke(...)方法完成实际的hello()方法所配置的远程URL的HTTP请求和结果的处理。
如果MethodHandler内的RPC调用出现异常,比如远程server宕机、网络延迟太大而导致请求超时、远程server来不及响应等,hystrixCommand命令器就会调用失败回调方法getFallback()返回回退结果。
而hystrixCommand的getFallback()方法最终会调用配置在RPC接口@FeignClient注解的fallback属性上的失败回退类中对应的回退方法,执行业务级别的失败回退处理。
使用HystrixInvocationHandler方法处理器进行远程调用,总体流程与使用默认的方法处理器FeignInvocationHandler进行远程调用大致是相同的。
以uaa-provider模块的DemoClient中hello()方法的远程调用执行过程为例,进行整体流程的展示,具体的时序图如图3-26所示。
图3-26 与HystrixInvocationHandler相关的远程调用执行流程
总体来说,使用HystrixInvocationHandler处理器的执行流程与使用FeignInvocationHandler默认的调用处理器相比大致是相同的。不同的是,HystrixInvocationHandler增加了RPC的保护机制。
Feign是一个声明式的RPC调用组件,它整合了Ribbon和Hystrix,使得服务调用更加简单。Feign提供了HTTP请求的模板,通过编写简单的接口和方法注解就可以定义好HTTP请求的参数、格式、地址等信息。
Feign极大地简化了RPC远程调用,大家只需要像调用普通方法一样就可以完成RPC远程调用。
Feign远程调用的核心是通过一系列封装和处理,将以JAVA注解方式定义的RPC方法最终转换成HTTP请求,然后将HTTP请求的响应结果解码成POJO对象返回给调用者。
Feign远程调用的完整流程如图3-27所示。
图3-27 Feign远程调用的完整流程
从图3-27可以看到,Feign通过对RPC注解的解析将请求模板化。当实际调用时传入参数,再根据参数应用到请求模板上,进而转化成真正的Request请求。通过Feign及其动态代理机制,Java开发人员不用再通过HTTP框架封装HTTP请求报文的方式完成远程服务的HTTP调用。
Spring Cloud Feign具有如下特性:
(1)可插拔的注解支持,包括Feign注解和Spring MVC注解。
(2)支持可插拔的HTTP编码器和解码器。
(3)支持Hystrix和它的RPC保护机制。
(4)支持Ribbon的负载均衡。
(5)支持HTTP请求和响应的压缩。
总体来说,使用Spring Cloud Feign组件本身整合了Ribbon和Hystrix,可设计一套稳定可靠的弹性客户端调用方案,避免整个系统出现雪崩效应。