为您推荐:
精华内容
最热下载
问答
  • 46KB houson_c 2018-12-16 02:21:42
  • 2.Open Feign vs Spring Cloud Feign 2.1.OpenFeign 2.2.Spring Cloud Open Feign 3.Spring Cloud Feign 的使用 4.Spring Cloud Feign 的源码解析 1.什么是Feign? Feign 的初衷是:feign makes writing java.....

    目录

    1.什么是Feign?

    2.Open Feign vs Spring Cloud Feign

    2.1.OpenFeign

    2.2.Spring Cloud Open Feign 

    3.Spring Cloud Feign 的使用

    4.Spring Cloud Feign 的源码解析


    1.什么是Feign?

    Feign 的初衷是:feign makes writing java http clients easier ,可以理解为一个Http Client。

    只不过这个http client 对http 请求进行了一个封装。后面我们会讲到它的一个工作方式就是处理注解,封装参数,放入到一个Http请求模板,并能解析返回的结果。

    2.Open Feign vs Spring Cloud Feign

    2.1.OpenFeign

    OpenFeign 是最原始,最早的feign。与Spring 无关。就是一个Java的组件,封装了对http请求和响应的处理。

    它的具体demo可以参考 Readme.txt

    2.2.Spring Cloud Open Feign 

    Spring Cloud 中的微服务都是以Http 接口的形式向外提供服务。

    提供Http 服务的形式有多种:

    • JDK 原生的URLConnction
    • Apache 的HttpClient
    • Spring 的RestTemplate

    Spring Cloud 对Feign 也进行了增强,直接支持Hystrix 和 Ribbon,也支持SpringMVC 的注解。这样使得Feign 的使用非常方便。

    3.Spring Cloud Feign 的使用

    在Spring Cloud 1.XX 系列版本中,feign 没有被单独拿出来,而是放在spring-cloud-netflix 下面。依赖的pom是:

    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-feign</artifactId>
    </dependency>

    而到了Spring Cloud 2.XX 系列版本后,feign 成为了Spring Cloud 下的一级项目,项目地址是:

    https://spring.io/projects/spring-cloud-openfeign

    依赖的pom是:

    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

    一定要注意的版本的兼容性,否则项目会启动不了。很多错误都有可能是这种版本不兼容导致的。

    现在的版本兼容状况可以参考:https://spring.io/projects/spring-cloud

    Table 1. Release train Spring Boot compatibility
    Release TrainBoot Version

    Greenwich

    2.1.x

    Finchley

    2.0.x

    Edgware

    1.5.x

    Dalston

    1.5.x

    上面的两个pom中,都没有写明version,这是因为使用Spring Cloud 都会集成dependencyManagement。这里会指定Spring-Cloud的版本。那它下面的组件版本就会和它保持一致

        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Finchley.SR2</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>

    a.首先要在主类上增加注解@EnableFeignClients

    @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
    @EnableEurekaClient
    @EnableFeignClients
    public class ConsumerApp
    {
        public static void main( String[] args )
        {
            SpringApplication.run(ConsumerApp.class,args);
        }
    }

    b.声明一个调用Feign的接口,可以指定服务,接口参数,接口地址,请求方法等

    @FeignClient("producer-1")
    public interface ConsumerFeignClient {
    
        @GetMapping("/getProducerUser")
        String getProducerUser();
    }

    c.服务提供者接口编写

        @GetMapping("/getProducerUser")
        public String getProducerUser(){
            log.info("8004 producer-1 getUser");
            return "8004 producer-1";
        }

    d.将接口注入到调用处使用

        @Autowired
        private ConsumerFeignClient consumerFeignClient;
    
        @GetMapping("/getProducerUser")
        public String getProducerUser(){
            log.info("consumer getProducerUser");
            return consumerFeignClient.getProducerUser();
        }

    这样就可以完成Feign 的整个调用了。

    可以看到,Spring Cloud Feign 是从 Open Feign发展而来,又集成了很多自己的东西,比如对@RequestMapping,@GetMapping等的支持。

    另外需要注意:

    Open Feign 模式是开启Retry(重试)机制的,而Spring Cloud Feign 默认是不开启Retry 的,但支持配置打开。

    4.Spring Cloud Feign 的源码解析

    Feign的原理步骤可以理解为:

    a.通过主类上的EnableFeignClients 注解开启FeignClient;

    b.根据Feign 的规则实现接口,并加上FeignClient注解,供调用的地方注入调用;

    c.程序启动后,会扫描所有FeignClient 注解的类,并将这些信息注入到IOC 容器中;

    d.当b中接口被调用时,通过jdk代理,以及反射(Spring处理注解的方式),来生成具体的RequestTemplate

    e.RequestTemplate 生成Reqest

    f.Request 交给httpclient处理,这里的httpclient 可以是OkHttp,也可以是HttpUrlConnection 或者HttpClient

    g.最后Client被封装到LoadBalanceClient类,这个类结合Ribbon 实现负载均衡

    参考:

    深入理解Feign之源码解析

    feign client 浅谈

    快速使用Spring Cloud Feign作为客户端调用服务提供者

    展开全文
    u014209205 2019-05-05 16:17:37
  • 经过微服务精通之Ribbon原理解析的学习,我们了解到了服务消费者获取服务提供者实例的过程,都是通过RestTemplate来实现的,而且,都是模板化操作。那spring cloud是否有哪个组件可以通过注解或者配置的方式,来简化...

    前言

           经过微服务精通之Ribbon原理解析的学习,我们了解到了服务消费者获取服务提供者实例的过程,都是通过RestTemplate来实现的,而且,都是模板化操作。那spring cloud是否有哪个组件可以通过注解或者配置的方式,来简化这个过程?答案是有的,就是Feign。


    一、Feign是什么?

           Feign是一个声明式的伪HTTP客户端,它使得HTTP请求变得更简单。使用Feign,只需要创建一个接口并注解。它具有可插拔注释支持,包括Feign注解和JAX-RS注解、Feign还支持可插拔编码器和解码器、Spring Cloud增加了对Spring MVC注释的支持。Feign默认集成了Ribbon,并和Eureka结合,默认实现了负载均衡的效果。

    二、Feign原理解析

    1.总体流程

    在这里插入图片描述

    2.Hystrix支持

           Feign是自带Hystrix熔断器的,不过在D版本之后,熔断器默认是关闭的,需要通过如下配置进行开启。

    feign.hystrix.enabled=true
    

           Feign启动熔断器后,可以在@FeignClient中指定熔断器类的方式,实现熔断器。每个接口的fallback方法为熔断器类中相同方法名的方法。

    3.请求压缩

           Feign支持通过配置的方式,开启请求和返回的GZIP压缩,减少请求的网络消耗。


    三、Feign实战

    1.Feign服务提供者

           沿用微服务精通之Ribbon原理解析中的service-hi服务。

    2.创建Feign消费者

    (1)创建名为service-feign的maven工程

    (2)引入Eureka-client和Feign依赖

    <?xml version="1.0"?>
    <project
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
    	xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    	<modelVersion>4.0.0</modelVersion>
    	<parent>
    		<groupId>com.hxq</groupId>
    		<artifactId>spring-cloud-hxq</artifactId>
    		<version>0.0.1-SNAPSHOT</version>
    	</parent>
    	<artifactId>service-feign</artifactId>
    	<name>service-feign</name>
    	
    	<dependencies>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-web</artifactId>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.cloud</groupId>
    			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.cloud</groupId>
    			<artifactId>spring-cloud-starter-openfeign</artifactId>
    		</dependency>
    	</dependencies>
    </project>
    
    

           (1)Eureka-client依赖:spring-cloud-starter-netflix-eureka-client;
           (2)Feign依赖:spring-cloud-starter-openfeign。

    (3)创建启动类

    package com.hxq;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    /**
     * 服务消费者
     * 
     * @author Administrator
     *
     */
    @SpringBootApplication
    @EnableEurekaClient
    @EnableDiscoveryClient
    @EnableFeignClients
    public class ServiceFeignApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ServiceFeignApplication.class, args);
        }
    
    }
    
    

           (1)启动类要加上@EnableEurekaClient注解和@EnableDiscoveryClient注解,使程序注册到Eureka。
           (2)启动类要加上@EnableFeignClients注解,是程序支持Feign操作。

    (4)创建一个测试接口

    package com.hxq.controller;
    
    import javax.annotation.Resource;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.hxq.service.HelloService;
    
    @RestController
    public class HelloController {
    
        @Resource
        private HelloService helloService;
        
        @RequestMapping("/hi")
        public String hi(@RequestParam(value="name", defaultValue="hxq")String name) {
            return helloService.hiService(name);
        }
    }
    
    
    package com.hxq.service;
    
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    
    @FeignClient(value = "service-hi", fallback = HelloServiceHystrix.class)
    public interface HelloService {
    
        @RequestMapping("/hi")
        String hiService(@RequestParam("name") String name);
    }
    
    

    (5)创建熔断器Fallback类

    package com.hxq.service.hystrix;
    
    import org.springframework.stereotype.Component;
    
    import com.hxq.service.HelloService;
    
    @Component("helloServiceHystrix")
    public class HelloServiceHystrix implements HelloService {
    
        @Override
        public String hiService(String name) {
            return "hi," + name + ",sorry,error!";
        }
    
    }
    
    

    (6)创建application.yml配置文件

    server:
        port: 9700
        
    spring:
        application:
            name: service-feign
    
    eureka:
        serviceUrl:
            defaultZone: http://localhost:8761/eureka/
    
    feign:
        hystrix:
            enabled: true
        compression:
            request:
                enabled: true
            response:
                enabled: true
    

           配置说明:
                  server.port:Feign消费者服务端口
                  eureka.serviceUrl.defaultZone:Eureka服务器的地址,类型为HashMap,缺省的Key为 defaultZone;缺省的Value为 http://localhost:8761/eureka。如果服务注册中心为高可用集群时,多个注册中心地址以逗号分隔。
                  spring.application.name:应用名称,将会显示在Eureka界面的应用名称列。
                  feign.hystrix.enabled:Hystrix开关。
                  feign.compression.request.enabled:请求GZIP压缩开关。
                  feign.compression.response.enabled:返回GZIP压缩开关。

    3.服务验证

    (1)启动service-hi服务

    在这里插入图片描述

    (2)启动service-feign服务

    在这里插入图片描述

    (3)调用测试接口

           在浏览器中输入http://localhost:9700/hi,运行结果如下图,可以看到接口返回service-hi服务的数据。
    在这里插入图片描述

    四、微服务精通系列文章

    展开全文
    qq_18153015 2020-11-01 23:53:37
  • Feign原理 (核心图解) 疯狂创客圈 Java 高并发【 亿级流量聊天室实战】实战系列 【博客园总入口 】 疯狂创客圈 正在进行分布式和高并发基础原理的研习,进行已经发布一些基础性的文章: 一、版本1 :springcloud...
     
    

    推荐: 地表最强 开发环境 系列

    工欲善其事 必先利其器
    地表最强 开发环境: vagrant+java+springcloud+redis+zookeeper镜像下载(&制作详解)
    地表最强 热部署:java SpringBoot SpringCloud 热部署 热加载 热调试
    地表最强 发请求工具(再见吧, PostMan ):IDEA HTTP Client(史上最全)
    地表最强 PPT 小工具: 屌炸天,像写代码一样写PPT
    无编程不创客,无编程不创客,一大波编程高手正在疯狂创客圈交流、学习中! 找组织,GO

    推荐: springCloud 微服务 系列

    推荐阅读
    nacos 实战(史上最全)
    sentinel (史上最全+入门教程)
    springcloud + webflux 高并发实战
    Webflux(史上最全)
    SpringCloud gateway (史上最全)
    无编程不创客,无编程不创客,一大波编程高手正在疯狂创客圈交流、学习中! 找组织,GO

    1SpringCloud 中 Feign 核心原理

    如果不了解 SpringCloud 中 Feign 核心原理,不会真正的了解 SpringCloud 的性能优化和配置优化,也就不可能做到真正掌握 SpringCloud。

    本章从Feign 远程调用的重要组件开始,图文并茂的介绍 Feigh 远程调用的执行流程、Feign 本地 JDK Proxy 实例的创建流程,彻底的为大家解读 SpringCloud 的核心知识。使得广大的工程师不光做到知其然,更能知其所以然。

    1.1 简介:Feign远程调用的基本流程

    Feign远程调用,核心就是通过一系列的封装和处理,将以JAVA注解的方式定义的远程调用API接口,最终转换成HTTP的请求形式,然后将HTTP的请求的响应结果,解码成JAVA Bean,放回给调用者。Feign远程调用的基本流程,大致如下图所示。
    在这里插入图片描述

    图1 Feign远程调用的基本流程

    从上图可以看到,Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的 Request 请求。通过Feign以及JAVA的动态代理机制,使得Java 开发人员,可以不用通过HTTP框架去封装HTTP请求报文的方式,完成远程服务的HTTP调用。

    1.2 Feign 远程调用的重要组件

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

    为了清晰的介绍SpringCloud中Feign运行机制和原理,在这里,首先为大家梳理一下Feign中几个重要组件。

    1.2.1 远程接口的本地JDK Proxy代理实例

    远程接口的本地JDK Proxy代理实例,有以下特点:

    (1)Proxy代理实例,实现了一个加 @FeignClient 注解的远程调用接口;

    (2)Proxy代理实例,能在内部进行HTTP请求的封装,以及发送HTTP 请求;

    (3)Proxy代理实例,能处理远程HTTP请求的响应,并且完成结果的解码,然后返回给调用者。

    下面以一个简单的远程服务的调用接口 DemoClient 为例,具体介绍一下远程接口的本地JDK Proxy代理实例的创建过程。

    DemoClient 接口,有两个非常简单的远程调用抽象方法:一个为hello() 抽象方法,用于完成远程URL “/api/demo/hello/v1”的HTTP请求;一个为 echo(…) 抽象方法,用于完成远程URL “/api/demo/echo/{word}/v1”的HTTP请求。具体如下图所示。
    在这里插入图片描述

    ​ 图2 远程接口的本地JDK Proxy代理实例示意图

    DemoClient 接口代码如下:

    
    package com.crazymaker.springcloud.demo.contract.client;
    //…省略import
    
    @FeignClient(
            value = "seckill-provider", path = "/api/demo/",
            fallback = DemoDefaultFallback.class)
    public interface DemoClient {
    
        /**
         * 测试远程调用
         *
         * @return hello
         */
        @GetMapping("/hello/v1")
        Result<JSONObject> hello();
    
    
        /**
         * 非常简单的一个 回显 接口,主要用于远程调用
         *
         * @return echo 回显消息
         */
        @RequestMapping(value = "/echo/{word}/v1", method = RequestMethod.GET)
        Result<JSONObject> echo(
                @PathVariable(value = "word") String word);
    
    }
    

    注意,上面的代码中,在DemoClient 接口上,加有@FeignClient 注解。也即是说,Feign在启动时,会为其创建一个本地JDK Proxy代理实例,并注册到Spring IOC容器。

    如何使用呢?可以通过@Resource注解,按照类型匹配(这里的类型为DemoClient接口类型),从Spring IOC容器找到这个代理实例,并且装配给需要的成员变量。

    DemoClient的 本地JDK Proxy 代理实例的使用的代码如下:

    
    package com.crazymaker.springcloud.user.info.controller;
    //…省略import
    @Api(value = "用户信息、基础学习DEMO", tags = {"用户信息、基础学习DEMO"})
    @RestController
    @RequestMapping("/api/user")
    public class UserController {
    
        @Resource
    DemoClient demoClient;  //装配 DemoClient 的本地代理实例
    
        @GetMapping("/say/hello/v1")
        @ApiOperation(value = "测试远程调用速度")
        public Result<JSONObject> hello() {
            Result<JSONObject> result = demoClient.hello();
            JSONObject data = new JSONObject();
            data.put("others", result);
            return Result.success(data).setMsg("操作成功");
        }
    //…
    }
    
    

    DemoClient的本地JDK Proxy代理实例的创建过程,比较复杂,稍后作为重点介绍。先来看另外两个重要的逻辑组件。

    1.2.2 调用处理器 InvocationHandler

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

    为了创建Feign的远程接口的代理实现类,Feign提供了自己的一个默认的调用处理器,叫做 FeignInvocationHandler 类,该类处于 feign-core 核心jar包中。当然,调用处理器可以进行替换,如果Feign与Hystrix结合使用,则会替换成 HystrixInvocationHandler 调用处理器类,类处于 feign-hystrix 的jar包中。
    在这里插入图片描述

    ​ 图3 Feign中实现的 InvocationHandler 调用处理器

    1.2.1 默认的调用处理器 FeignInvocationHandler

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

    以前面示例中DemoClient 接口为例,其代理实现类的调用处理器 FeignInvocationHandler 的dispatch 成员的内存结构图如图3所示。
    在这里插入图片描述

    图4 DemoClient代理实例的调用处理器 FeignInvocationHandler的dispatch 成员

    为何在图3中的Map类型成员 dispatch 映射对象中,有两个Key-Value键值对呢?

    原因是:默认的调用处理器 FeignInvocationHandle,在处理远程方法调用的时候,会根据Java反射的方法实例,在dispatch 映射对象中,找到对应的MethodHandler 方法处理器,然后交给MethodHandler 完成实际的HTTP请求和结果的处理。前面示例中的 DemoClient 远程调用接口,有两个远程调用方法,所以,其代理实现类的调用处理器 FeignInvocationHandler 的dispatch 成员,有两个有两个Key-Value键值对。

    FeignInvocationHandler的关键源码,节选如下:

    package feign;
    //...省略import
    
    public class ReflectiveFeign extends Feign {
    
      //...
    
      //内部类:默认的Feign调用处理器 FeignInvocationHandler
      static class FeignInvocationHandler implements InvocationHandler {
    
        private final Target target;
        //方法实例对象和方法处理器的映射
        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 {
          //...
    	  //首先,根据方法实例,从方法实例对象和方法处理器的映射中,
    	  //取得 方法处理器,然后,调用 方法处理器 的 invoke(...) 方法
             return dispatch.get(method).invoke(args);
        }
        //...
      }
     
    

    源码很简单,重点在于invoke(…)方法,虽然核心代码只有一行,但是其功能是复杂的:

    (1)根据Java反射的方法实例,在dispatch 映射对象中,找到对应的MethodHandler 方法处理器;

    (2)调用MethodHandler方法处理器的 invoke(…) 方法,完成实际的HTTP请求和结果的处理。

    补充说明一下:MethodHandler 方法处理器,和JDK 动态代理机制中位于 java.lang.reflect 包的 InvocationHandler 调用处理器接口,没有任何的继承和实现关系。MethodHandler 仅仅是Feign自定义的,一个非常简单接口。

    1.2.2 方法处理器 MethodHandler

    Feign的方法处理器 MethodHandler 是一个独立的接口,定义在 InvocationHandlerFactory 接口中,仅仅拥有一个invoke(…)方法,源码如下:

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

    MethodHandler 的invoke(…)方法,主要职责是完成实际远程URL请求,然后返回解码后的远程URL的响应结果。Feign提供了默认的 SynchronousMethodHandler 实现类,提供了基本的远程URL的同步请求处理。有关 SynchronousMethodHandler类以及其与MethodHandler的关系,大致如图4所示。
    在这里插入图片描述

    图5 Feign的MethodHandler方法处理器

    为了彻底了解方法处理器,来读一下 SynchronousMethodHandler 方法处理器的源码,大致如下:

    package feign;
    //…..省略import
    final class SynchronousMethodHandler implements MethodHandler {
      //…
      // 执行Handler 的处理
    public Object invoke(Object[] argv) throws Throwable {
            RequestTemplate requestTemplate = this.buildTemplateFromArgs.create(argv);
            Retryer retryer = this.retryer.clone();
    
            while(true) {
                try {
                    return this.executeAndDecode(requestTemplate);
                } catch (RetryableException var5) {
                   //…省略不相干代码
                }
            }
    }
    
      //执行请求,然后解码结果
    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(…)方法,调用了自己的executeAndDecode(…) 请求执行和结果解码方法。该方法的工作步骤:

    (1)首先通 RequestTemplate 请求模板实例,生成远程URL请求实例 request;

    (2)然后用自己的 feign 客户端client成员,excecute(…) 执行请求,并且获取 response 响应;

    (3)对response 响应进行结果解码。

    1.2.3 Feign 客户端组件 feign.Client

    客户端组件是Feign中一个非常重要的组件,负责端到端的执行URL请求。其核心的逻辑:发送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.Client 有多个不同的实现。这里举出几个例子:

    (1)Client.Default类:默认的feign.Client 客户端实现类,内部使用HttpURLConnnection 完成URL请求处理;

    (2)ApacheHttpClient 类:内部使用 Apache httpclient 开源组件完成URL请求处理的feign.Client 客户端实现类;

    (3)OkHttpClient类:内部使用 OkHttp3 开源组件完成URL请求处理的feign.Client 客户端实现类。

    (4)LoadBalancerFeignClient 类:内部使用 Ribben 负载均衡技术完成URL请求处理的feign.Client 客户端实现类。

    此外,还有一些特殊场景使用的feign.Client客户端实现类,也可以定制自己的feign.Client实现类。下面对上面几个常见的客户端实现类,进行简要介绍。
    在这里插入图片描述

    ​ 图6 feign.Client客户端实现类

    一:Client.Default类:

    作为默认的Client 接口的实现类,在Client.Default内部使用JDK自带的HttpURLConnnection类实现URL网络请求。

    图片

    ​ 图7 默认的Client 接口的客户端实现类

    在JKD1.8中,虽然在HttpURLConnnection 底层,使用了非常简单的HTTP连接池技术,但是,其HTTP连接的复用能力,实际是非常弱的,性能当然也很低。具体的原因,参见后面的“SpringCloud与长连接的深入剖析”专题内容。

    二:ApacheHttpClient类

    ApacheHttpClient 客户端类的内部,使用 Apache HttpClient开源组件完成URL请求的处理。

    从代码开发的角度而言,Apache HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性,它不仅使客户端发送Http请求变得容易,而且也方便开发人员测试接口。既提高了开发的效率,也方便提高代码的健壮性。

    从性能的角度而言,Apache HttpClient带有连接池的功能,具备优秀的HTTP连接的复用能力。关于带有连接池Apache HttpClient的性能提升倍数,具体可以参见后面的对比试验。

    ApacheHttpClient 类处于 feign-httpclient 的专门jar包中,如果使用,还需要通过Maven依赖或者其他的方式,倒入配套版本的专门jar包。

    三:OkHttpClient类

    OkHttpClient 客户端类的内部,使用OkHttp3 开源组件完成URL请求处理。OkHttp3 开源组件由Square公司开发,用于替代HttpUrlConnection和Apache HttpClient。由于OkHttp3较好的支持 SPDY协议(SPDY是Google开发的基于TCP的传输层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。),从Android4.4开始,google已经开始将Android源码中的 HttpURLConnection 请求类使用OkHttp进行了替换。也就是说,对于Android 移动端APP开发来说,OkHttp3 组件,是基础的开发组件之一。

    四:LoadBalancerFeignClient 类

    LoadBalancerFeignClient 内部使用了 Ribben 客户端负载均衡技术完成URL请求处理。在原理上,简单的使用了delegate包装代理模式:Ribben负载均衡组件计算出合适的服务端server之后,由内部包装 delegate 代理客户端完成到服务端server的HTTP请求;所封装的 delegate 客户端代理实例的类型,可以是 Client.Default 默认客户端,也可以是 ApacheHttpClient 客户端类或OkHttpClient 高性能客户端类,还可以其他的定制的feign.Client 客户端实现类型。

    LoadBalancerFeignClient 负载均衡客户端实现类,具体如下图所示。
    在这里插入图片描述

    ​ 图8 LoadBalancerFeignClient 负载均衡客户端实现类

    1.1 Feigh 远程调用的执行流程

    由于Feign远程调用接口的JDK Proxy实例的InvokeHandler调用处理器有多种,导致Feign远程调用的执行流程,也稍微有所区别,但是远程调用执行流程的主要步骤,是一致的。这里主要介绍两类JDK Proxy实例的InvokeHandler调用处理器相关的远程调用执行流程:

    (1)与 默认的调用处理器 FeignInvocationHandler 相关的远程调用执行流程;

    (2)与 Hystrix调用处理器 HystrixInvocationHandler 相关的远程调用执行流程。

    介绍过程中,还是以前面的DemoClient的JDK Proxy远程动态代理实例的执行过程为例,演示分析Feigh远程调用的执行流程。

    1.1.1 与 FeignInvocationHandler 相关的远程调用执行流程

    FeignInvocationHandler是默认的调用处理器,如果不对Feign做特殊的配置,则Feign将使用此调用处理器。结合前面的DemoClient的JDK Proxy远程动态代理实例的hello()远程调用执行过程,在这里,详细的介绍一下与 FeignInvocationHandler 相关的远程调用执行流程,大致如下图所示。
    在这里插入图片描述

    图6 与 FeignInvocationHandler 相关的远程调用执行流程

    整体的远程调用执行流程,大致分为4步,具体如下:

    第1步:通过Spring IOC 容器实例,装配代理实例,然后进行远程调用。

    前文讲到,Feign在启动时,会为加上了@FeignClient注解的所有远程接口(包括 DemoClient 接口),创建一个本地JDK Proxy代理实例,并注册到Spring IOC容器。在这里,暂且将这个Proxy代理实例,叫做 DemoClientProxy,稍后,会详细介绍这个Proxy代理实例的具体创建过程。

    然后,在本实例的UserController 调用代码中,通过@Resource注解,按照类型或者名称进行匹配(这里的类型为DemoClient接口类型),从Spring IOC容器找到这个代理实例,并且装配给@Resource注解所在的成员变量,本实例的成员变量的名称为 demoClient。

    在需要代进行hello()远程调用时,直接通过 demoClient 成员变量,调用JDK Proxy动态代理实例的hello()方法。

    第2步:执行 InvokeHandler 调用处理器的invoke(…)方法

    前面讲到,JDK Proxy动态代理实例的真正的方法调用过程,具体是通过 InvokeHandler 调用处理器完成的。故,这里的DemoClientProxy代理实例,会调用到默认的FeignInvocationHandler 调用处理器实例的invoke(…)方法。

    通过前面 FeignInvocationHandler 调用处理器的详细介绍,大家已经知道,默认的调用处理器 FeignInvocationHandle,内部保持了一个远程调用方法实例和方法处理器的一个Key-Value键值对Map映射。FeignInvocationHandle 在其invoke(…)方法中,会根据Java反射的方法实例,在dispatch 映射对象中,找到对应的 MethodHandler 方法处理器,然后由后者完成实际的HTTP请求和结果的处理。

    所以在第2步中,FeignInvocationHandle 会从自己的 dispatch映射中,找到hello()方法所对应的MethodHandler 方法处理器,然后调用其 invoke(…)方法。

    第3步:执行 MethodHandler 方法处理器的invoke(…)方法

    通过前面关于 MethodHandler 方法处理器的非常详细的组件介绍,大家都知道,feign默认的方法处理器为 SynchronousMethodHandler,其invoke(…)方法主要是通过内部成员feign客户端成员 client,完成远程 URL 请求执行和获取远程结果。

    feign.Client 客户端有多种类型,不同的类型,完成URL请求处理的具体方式不同。

    第4步:通过 feign.Client 客户端成员,完成远程 URL 请求执行和获取远程结果

    如果MethodHandler方法处理器实例中的client客户端,是默认的 feign.Client.Default 实现类性,则使用JDK自带的HttpURLConnnection类,完成远程 URL 请求执行和获取远程结果。

    如果MethodHandler方法处理器实例中的client客户端,是 ApacheHttpClient 客户端实现类性,则使用 Apache httpclient 开源组件,完成远程 URL 请求执行和获取远程结果。

    通过以上四步,应该可以清晰的了解到了 SpringCloud中的 feign 远程调用执行流程和运行机制。

    实际上,为了简明扼要的介绍清楚默认的调用流程,上面的流程,实际上省略了一个步骤:第3步,实际可以分为两小步。为啥呢? SynchronousMethodHandler 并不是直接完成远程URL的请求,而是通过负载均衡机制,定位到合适的远程server 服务器,然后再完成真正的远程URL请求。换句话说,SynchronousMethodHandler实例的client成员,其实际不是feign.Client.Default类型,而是 LoadBalancerFeignClient 客户端负载均衡类型。 因此,上面的第3步,如果进一步细分话,大致如下:(1)首先通过 SynchronousMethodHandler 内部的client实例,实质为负责客户端负载均衡 LoadBalancerFeignClient 实例,首先查找到远程的 server 服务端;(2) 然后再由LoadBalancerFeignClient 实例内部包装的feign.Client.Default 内部类实例,去请求server端服务器,完成URL请求处理。

    最后,说明下,默认的与 FeignInvocationHandler 相关的远程调用执行流程,在运行机制以及调用性能上,满足不了生产环境的要求,为啥呢? 大致原因有以下两点:

    (1) 没有远程调用过程中的熔断监测和恢复机制;

    (2) 也没有用到高性能的HTTP连接池技术。

    接下来,将为大家介绍一下用到熔断监测和恢复机制 Hystrix 技术的远程调用执行流程,该流程中,远程接口的JDK Proxy动态代理实例所使用的调用处理器,叫做 HystrixInvocationHandler 调用处理器。

    本文的内容,在《SpringCloud Nginx高并发核心编程》一书时,进行内容的完善和更新,并且进行的源码的升级。 博客和书不一样,书更加层层升入、层次分明,请大家以书的内容为准。

    在这里插入图片描述

    具体,请关注 Java 高并发研习社群博客园 总入口


    回到◀疯狂创客圈

    疯狂创客圈 - Java高并发研习社群,为大家开启大厂之门

    展开全文
    crazymakercircle 2019-12-01 13:06:54
  • 1.1简介:Feign远程调用的 Feign远程调用,核心就是通过一系列的封装和处理,将以JAVA注解的方式定义的远程调用API接口,最终转换成HTTP的请求形式,然后将HTTP的请求的响应结果,解码成JAVA Bean,放回给调用...

    1.1 简介:Feign远程调用的

     

     

    Feign远程调用,核心就是通过一系列的封装和处理,将以JAVA注解的方式定义的远程调用API接口,最终转换成HTTP的请求形式,然后将HTTP的请求的响应结果,解码成JAVA Bean,放回给调用者。Feign远程调用的基本流程,大致如下图所示。

     

    å¨è¿éæå¥å¾çæè¿°

    从上图可以看到,Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的 Request 请求。通过Feign以及JAVA的动态代理机制,使得Java 开发人员,可以不用通过HTTP框架去封装HTTP请求报文的方式,完成远程服务的HTTP调用。

     

    1.2 Feign 远程调用的重要组件

     

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

    为了清晰的介绍SpringCloud中Feign运行机制和原理,在这里,首先为大家梳理一下Feign中几个重要组件。

     

    1.2.1 远程接口的本地JDK Proxy代理实例

     

    远程接口的本地JDK Proxy代理实例,有以下特点:

    (1)Proxy代理实例,实现了一个加 @FeignClient 注解的远程调用接口;

    (2)Proxy代理实例,能在内部进行HTTP请求的封装,以及发送HTTP 请求;

    (3)Proxy代理实例,能处理远程HTTP请求的响应,并且完成结果的解码,然后返回给调用者。

    下面以一个简单的远程服务的调用接口 DemoClient 为例,具体介绍一下远程接口的本地JDK Proxy代理实例的创建过程。

    DemoClient 接口,有两个非常简单的远程调用抽象方法:一个为hello() 抽象方法,用于完成远程URL “/api/demo/hello/v1”的HTTP请求;一个为 echo(…) 抽象方法,用于完成远程URL “/api/demo/echo/{word}/v1”的HTTP请求。具体如下图所示。

     

    å¨è¿éæå¥å¾çæè¿°

     

    DemoClient 接口代码如下:

     

           package com.crazymaker.springcloud.demo.contract.client;
    //…省略import
    
          @FeignClient(
                value = "seckill-provider", path = "/api/demo/",
                fallback = DemoDefaultFallback.class)
          public interface DemoClient {
    
             /**
              * 测试远程调用
              *
              * @return hello
              */
             @GetMapping("/hello/v1")
             Result<JSONObject> hello();
    
    
             /**
              * 非常简单的一个 回显 接口,主要用于远程调用
              *
              * @return echo 回显消息
              */
             @RequestMapping(value = "/echo/{word}/v1", method = RequestMethod.GET)
             Result<JSONObject> echo(
                   @PathVariable(value = "word") String word);
    
          }

     

     

    注意,上面的代码中,在DemoClient 接口上,加有@FeignClient 注解。也即是说,Feign在启动时,会为其创建一个本地JDK Proxy代理实例,并注册到Spring IOC容器。

    如何使用呢?可以通过@Resource注解,按照类型匹配(这里的类型为DemoClient接口类型),从Spring IOC容器找到这个代理实例,并且装配给需要的成员变量。

    DemoClient的 本地JDK Proxy 代理实例的使用的代码如下:

     

    package com.crazymaker.springcloud.user.info.controller;
    //…省略import
          @Api(value = "用户信息、基础学习DEMO", tags = {"用户信息、基础学习DEMO"})
          @RestController
          @RequestMapping("/api/user")
          public class UserController {
    
             @Resource
             DemoClient demoClient;  //装配 DemoClient 的本地代理实例
    
             @GetMapping("/say/hello/v1")
             @ApiOperation(value = "测试远程调用速度")
             public Result<JSONObject> hello() {
                Result<JSONObject> result = demoClient.hello();
                JSONObject data = new JSONObject();
                data.put("others", result);
                return Result.success(data).setMsg("操作成功");
             }
    //…
          }
    

     

    DemoClient的本地JDK Proxy代理实例的创建过程,比较复杂,稍后作为重点介绍。先来看另外两个重要的逻辑组件。

    1.2.2 调用处理器 InvocationHandler

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

    以前面示例中DemoClient 接口为例,其代理实现类的调用处理器 FeignInvocationHandler 的dispatch 成员的内存结构图如图3所示。

     

    å¨è¿éæå¥å¾çæè¿°

     

    1.2.1 默认的调用处理器 FeignInvocationHandler

     

    å¨è¿éæå¥å¾çæè¿°

     

    为何在图3中的Map类型成员 dispatch 映射对象中,有两个Key-Value键值对呢?

    原因是:默认的调用处理器 FeignInvocationHandle,在处理远程方法调用的时候,会根据Java反射的方法实例,在dispatch 映射对象中,找到对应的MethodHandler 方法处理器,然后交给MethodHandler 完成实际的HTTP请求和结果的处理。前面示例中的 DemoClient 远程调用接口,有两个远程调用方法,所以,其代理实现类的调用处理器 FeignInvocationHandler 的dispatch 成员,有两个有两个Key-Value键值对。

    FeignInvocationHandler的关键源码,节选如下:

    package feign;
    //...省略import
    
          public class ReflectiveFeign extends Feign {
    
             //...
    
             //内部类:默认的Feign调用处理器 FeignInvocationHandler
             static class FeignInvocationHandler implements InvocationHandler {
    
                private final Target target;
                //方法实例对象和方法处理器的映射
                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 {
                   //...
                   //首先,根据方法实例,从方法实例对象和方法处理器的映射中,
                   //取得 方法处理器,然后,调用 方法处理器 的 invoke(...) 方法
                   return dispatch.get(method).invoke(args);
                }
                //...
             }

     

    源码很简单,重点在于invoke(…)方法,虽然核心代码只有一行,但是其功能是复杂的:

    (1)根据Java反射的方法实例,在dispatch 映射对象中,找到对应的MethodHandler 方法处理器;

    (2)调用MethodHandler方法处理器的 invoke(...) 方法,完成实际的HTTP请求和结果的处理。

    补充说明一下:MethodHandler 方法处理器,和JDK 动态代理机制中位于 java.lang.reflect 包的 InvocationHandler 调用处理器接口,没有任何的继承和实现关系。MethodHandler 仅仅是Feign自定义的,一个非常简单接口。

     

     

    1.2.2 方法处理器 MethodHandler

     

    Feign的方法处理器 MethodHandler 是一个独立的接口,定义在 InvocationHandlerFactory 接口中,仅仅拥有一个invoke(…)方法,源码如下:

     

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

     

    MethodHandler 的invoke(…)方法,主要职责是完成实际远程URL请求,然后返回解码后的远程URL的响应结果。Feign提供了默认的 SynchronousMethodHandler 实现类,提供了基本的远程URL的同步请求处理。有关 SynchronousMethodHandler类以及其与MethodHandler的关系,大致如图4所示。

     

    å¨è¿éæå¥å¾çæè¿°

    为了彻底了解方法处理器,来读一下 SynchronousMethodHandler 方法处理器的源码,如下:

    SynchronousMethodHandler(重中之重)

    同步方法调用处理器,它强调的是同步二字,且有远程通信。

    final class SynchronousMethodHandler implements MethodHandler {
    	
    	// 方法元信息
    	private final MethodMetadata metadata;
    	// 目标  也就是最终真正构建Http请求Request的实例 一般为HardCodedTarget
    	private final Target<?> target;
    	// 负责最终请求的发送 -> 默认传进来的是基于JDK源生的,效率很低,不建议直接使用
    	private final Client client;
    	// 负责重试 -->默认传进来的是Default,是有重试机制的哦,生产上使用请务必注意
    	private final Retryer retryer;
    
    	// 请求拦截器,它会在target.apply(template); 也就是模版 -> 请求的转换之前完成拦截
    	// 说明:并不是发送请求之前那一刻哦,请务必注意啦
    	// 它的作用只能是对请求模版做定制,而不能再对Request做定制了
    	// 内置仅有一个实现:BasicAuthRequestInterceptor 用于鉴权
    	private final List<RequestInterceptor> requestInterceptors;
    
    	// 若你想在控制台看到feign的请求日志,改变此日志级别为info吧(因为一般只有info才会输出到日志文件)
    	private final Logger.Level logLevel;
    	...
    	// 构建请求模版的工厂
    	// 对于请求模版,有多种构建方式,内部会用到可能多个编码器,下文详解
    	private final RequestTemplate.Factory buildTemplateFromArgs;
    	// 请求参数:比如链接超时时间、请求超时时间等
    	private final Options options;
    
    	// 解码器:用于对Response进行解码
    	private final Decoder decoder;
    	// 发生错误/异常时的解码器
    	private final ErrorDecoder errorDecoder;
    
    	// 是否解码404状态码?默认是不解码的
    	private final boolean decode404;
    	
    	// 唯一的构造器,并且还是私有的(所以肯定只能在本类内构建出它的实例喽)
    	// 完成了对如上所有属性的赋值
    	private SynchronousMethodHandler( ... ) { ... }
    
      	@Override
      	public Object invoke(Object[] argv) throws Throwable {
      		// 根据方法入参,结合工厂构建出一个请求模版
    		RequestTemplate template = buildTemplateFromArgs.create(argv);
    		// findOptions():如果你方法入参里含有Options类型这里会被找出来
    		// 说明:若有多个只会有第一个生效(不会报错)
    		Options options = findOptions(argv);
    		// 重试机制:注意这里是克隆一个来使用
    		Retryer retryer = this.retryer.clone();
    		while (true) {
    		     try {
    		        return executeAndDecode(template, options);
    		      } catch (RetryableException e) {
    				
    				// 若抛出异常,那就触发重试逻辑
    		       try {
    		       	  // 该逻辑是:如果不重试了,该异常会继续抛出
    		       	  // 若要充值,就会走下面的continue
    		          retryer.continueOrPropagate(e);
    		        } catch (RetryableException th) {
    		        	...
    		        }
    		        continue;
    		      }
    		}
      	}
    }

     

    MethodHandler实现相对复杂,用一句话描述便是:准备好所有参数后,发送Http请求,并且解析结果。它的步骤我尝试总结如下:

    1. 通过方法参数,使用工厂构建出一个RequestTemplate请求模版
      1. 这里会解析@RequestLine/@Param等等注解
    2. 从方法参数里拿到请求选项:Options(当然参数里可能也没有此类型,那就是null喽。如果是null,那最终执行默认的选项)
    3. executeAndDecode(template, options)执行发送Http请求,并且完成结果解码(包括正确状态码的解码和错误解码)。这个步骤比较复杂,拆分为如下子步骤:
      1. 把请求模版转换为请求对象feign.Request
        1. 执行所有的拦截器RequestInterceptor,完成对请求模版的定制
        2. 调用目标target,把请求模版转为Request:target.apply(template);
      2. 发送Http请求:client.execute(request, options),得到一个Response对象(这里若发生IO异常,也会被包装为RetryableException重新抛出)
      3. 解析此Response对象,解析后return(返回Object:可能是Response实例,也可能是decode解码后的任意类型)。大致会有如下情况:
        1. Response.class == metadata.returnType(),也就是说你的方法返回值用的就是Response。若response.body() == null,也就是说服务端是返回null/void的话,就直接return response;若response.body().length() == null,就直接返回response;否则,就正常返回response.toBuilder().body(bodyData).build() body里面的内容吧
        2. 200 <= 响应码 <= 300,表示正确的返回。那就对返回值解码即可:decoder.decode(response, metadata.returnType())(解码过程中有可能异常,也会被包装成FeignException向上抛出)
        3. 若响应码是404,并且decode404 = true,那同上也同样执行decode动作
        4. 其它情况(4xx或者5xx的响应码),均执行错误编码:errorDecoder.decode(metadata.configKey(), response)
    4. 发送http请求若一切安好,那就结束了。否则执行重试逻辑:
      1. 通过retryer.continueOrPropagate(e);看看收到此异常后是否要执行重试机制
      2. 需要重试的话就continue(注意上面是while(true)哦~)
      3. 若不需要重试(或者重试次数已到),那就重新抛出此异常,向上抛出
      4. 处理此异常,打印日志…
    5. 1.2.3 Feign 客户端组件 feign.Client

      客户端组件是Feign中一个非常重要的组件,负责端到端的执行URL请求。其核心的逻辑:发送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.Client 有多个不同的实现。这里举出几个例子:

      (1)Client.Default类:默认的feign.Client 客户端实现类,内部使用HttpURLConnnection 完成URL请求处理;

      (2)ApacheHttpClient 类:内部使用 Apache httpclient 开源组件完成URL请求处理的feign.Client 客户端实现类;

      (3)OkHttpClient类:内部使用 OkHttp3 开源组件完成URL请求处理的feign.Client 客户端实现类。

      (4)LoadBalancerFeignClient 类:内部使用 Ribben 负载均衡技术完成URL请求处理的feign.Client 客户端实现类。

      此外,还有一些特殊场景使用的feign.Client客户端实现类,也可以定制自己的feign.Client实现类。下面对上面几个常见的客户端实现类,进行简要介绍。
      在这里插入图片描述

      ​ 图6 feign.Client客户端实现类

      一:Client.Default类:

      作为默认的Client 接口的实现类,在Client.Default内部使用JDK自带的HttpURLConnnection类实现URL网络请求。

      图片

      ​ 图7 默认的Client 接口的客户端实现类

      在JKD1.8中,虽然在HttpURLConnnection 底层,使用了非常简单的HTTP连接池技术,但是,其HTTP连接的复用能力,实际是非常弱的,性能当然也很低。具体的原因,参见后面的“SpringCloud与长连接的深入剖析”专题内容。

      二:ApacheHttpClient类

      ApacheHttpClient 客户端类的内部,使用 Apache HttpClient开源组件完成URL请求的处理。

      从代码开发的角度而言,Apache HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性,它不仅使客户端发送Http请求变得容易,而且也方便开发人员测试接口。既提高了开发的效率,也方便提高代码的健壮性。

      从性能的角度而言,Apache HttpClient带有连接池的功能,具备优秀的HTTP连接的复用能力。关于带有连接池Apache HttpClient的性能提升倍数,具体可以参见后面的对比试验。

      ApacheHttpClient 类处于 feign-httpclient 的专门jar包中,如果使用,还需要通过Maven依赖或者其他的方式,倒入配套版本的专门jar包。

      三:OkHttpClient类

      OkHttpClient 客户端类的内部,使用OkHttp3 开源组件完成URL请求处理。OkHttp3 开源组件由Square公司开发,用于替代HttpUrlConnection和Apache HttpClient。由于OkHttp3较好的支持 SPDY协议(SPDY是Google开发的基于TCP的传输层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。),从Android4.4开始,google已经开始将Android源码中的 HttpURLConnection 请求类使用OkHttp进行了替换。也就是说,对于Android 移动端APP开发来说,OkHttp3 组件,是基础的开发组件之一。

      四:LoadBalancerFeignClient 类

      LoadBalancerFeignClient 内部使用了 Ribben 客户端负载均衡技术完成URL请求处理。在原理上,简单的使用了delegate包装代理模式:Ribben负载均衡组件计算出合适的服务端server之后,由内部包装 delegate 代理客户端完成到服务端server的HTTP请求;所封装的 delegate 客户端代理实例的类型,可以是 Client.Default 默认客户端,也可以是 ApacheHttpClient 客户端类或OkHttpClient 高性能客户端类,还可以其他的定制的feign.Client 客户端实现类型。

      LoadBalancerFeignClient 负载均衡客户端实现类,具体如下图所示。
      在这里插入图片描述

      ​ 图8 LoadBalancerFeignClient 负载均衡客户端实现类

      1.1 Feigh 远程调用的执行流程

      由于Feign远程调用接口的JDK Proxy实例的InvokeHandler调用处理器有多种,导致Feign远程调用的执行流程,也稍微有所区别,但是远程调用执行流程的主要步骤,是一致的。这里主要介绍两类JDK Proxy实例的InvokeHandler调用处理器相关的远程调用执行流程:

      (1)与 默认的调用处理器 FeignInvocationHandler 相关的远程调用执行流程;

      (2)与 Hystrix调用处理器 HystrixInvocationHandler 相关的远程调用执行流程。

      介绍过程中,还是以前面的DemoClient的JDK Proxy远程动态代理实例的执行过程为例,演示分析Feigh远程调用的执行流程。

      1.1.1 与 FeignInvocationHandler 相关的远程调用执行流程

      FeignInvocationHandler是默认的调用处理器,如果不对Feign做特殊的配置,则Feign将使用此调用处理器。结合前面的DemoClient的JDK Proxy远程动态代理实例的hello()远程调用执行过程,在这里,详细的介绍一下与 FeignInvocationHandler 相关的远程调用执行流程,大致如下图所示。
      在这里插入图片描述

      ​ 图6 与 FeignInvocationHandler 相关的远程调用执行流程

      整体的远程调用执行流程,大致分为4步,具体如下:

      第1步:通过Spring IOC 容器实例,装配代理实例,然后进行远程调用。

      前文讲到,Feign在启动时,会为加上了@FeignClient注解的所有远程接口(包括 DemoClient 接口),创建一个本地JDK Proxy代理实例,并注册到Spring IOC容器。在这里,暂且将这个Proxy代理实例,叫做 DemoClientProxy,稍后,会详细介绍这个Proxy代理实例的具体创建过程。

      然后,在本实例的UserController 调用代码中,通过@Resource注解,按照类型或者名称进行匹配(这里的类型为DemoClient接口类型),从Spring IOC容器找到这个代理实例,并且装配给@Resource注解所在的成员变量,本实例的成员变量的名称为 demoClient。

      在需要代进行hello()远程调用时,直接通过 demoClient 成员变量,调用JDK Proxy动态代理实例的hello()方法。

      第2步:执行 InvokeHandler 调用处理器的invoke(…)方法

      前面讲到,JDK Proxy动态代理实例的真正的方法调用过程,具体是通过 InvokeHandler 调用处理器完成的。故,这里的DemoClientProxy代理实例,会调用到默认的FeignInvocationHandler 调用处理器实例的invoke(…)方法。

      通过前面 FeignInvocationHandler 调用处理器的详细介绍,大家已经知道,默认的调用处理器 FeignInvocationHandle,内部保持了一个远程调用方法实例和方法处理器的一个Key-Value键值对Map映射。FeignInvocationHandle 在其invoke(…)方法中,会根据Java反射的方法实例,在dispatch 映射对象中,找到对应的 MethodHandler 方法处理器,然后由后者完成实际的HTTP请求和结果的处理。

      所以在第2步中,FeignInvocationHandle 会从自己的 dispatch映射中,找到hello()方法所对应的MethodHandler 方法处理器,然后调用其 invoke(…)方法。

      第3步:执行 MethodHandler 方法处理器的invoke(…)方法

      通过前面关于 MethodHandler 方法处理器的非常详细的组件介绍,大家都知道,feign默认的方法处理器为 SynchronousMethodHandler,其invoke(…)方法主要是通过内部成员feign客户端成员 client,完成远程 URL 请求执行和获取远程结果。

      feign.Client 客户端有多种类型,不同的类型,完成URL请求处理的具体方式不同。

      第4步:通过 feign.Client 客户端成员,完成远程 URL 请求执行和获取远程结果

      如果MethodHandler方法处理器实例中的client客户端,是默认的 feign.Client.Default 实现类性,则使用JDK自带的HttpURLConnnection类,完成远程 URL 请求执行和获取远程结果。

      如果MethodHandler方法处理器实例中的client客户端,是 ApacheHttpClient 客户端实现类性,则使用 Apache httpclient 开源组件,完成远程 URL 请求执行和获取远程结果。

      通过以上四步,应该可以清晰的了解到了 SpringCloud中的 feign 远程调用执行流程和运行机制。

      实际上,为了简明扼要的介绍清楚默认的调用流程,上面的流程,实际上省略了一个步骤:第3步,实际可以分为两小步。为啥呢? SynchronousMethodHandler 并不是直接完成远程URL的请求,而是通过负载均衡机制,定位到合适的远程server 服务器,然后再完成真正的远程URL请求。换句话说,SynchronousMethodHandler实例的client成员,其实际不是feign.Client.Default类型,而是 LoadBalancerFeignClient 客户端负载均衡类型。 因此,上面的第3步,如果进一步细分话,大致如下:(1)首先通过 SynchronousMethodHandler 内部的client实例,实质为负责客户端负载均衡 LoadBalancerFeignClient 实例,首先查找到远程的 server 服务端;(2) 然后再由LoadBalancerFeignClient 实例内部包装的feign.Client.Default 内部类实例,去请求server端服务器,完成URL请求处理。

      最后,说明下,默认的与 FeignInvocationHandler 相关的远程调用执行流程,在运行机制以及调用性能上,满足不了生产环境的要求,为啥呢? 大致原因有以下两点:

      (1) 没有远程调用过程中的熔断监测和恢复机制;

      (2) 也没有用到高性能的HTTP连接池技术。

      接下来,将为大家介绍一下用到熔断监测和恢复机制 Hystrix 技术的远程调用执行流程,该流程中,远程接口的JDK Proxy动态代理实例所使用的调用处理器,叫做 HystrixInvocationHandler 调用处理器。

    6.  

     

     

    展开全文
    xjk201 2020-07-28 21:17:51
  • nihui123 2020-06-29 17:00:54
  • chester_zheng 2021-08-10 11:27:50
  • weixin_40980639 2020-07-23 17:33:58
  • 2.03MB rongbo91 2020-07-16 15:28:10
  • summer_fish 2020-12-26 16:26:11
  • Zaric_001 2021-02-21 15:23:46
  • m0_47495420 2021-03-06 00:34:03
  • studyvcmfc 2020-12-11 15:53:48
  • a18262285324 2020-03-31 20:00:12
  • LarrYFinal 2021-10-26 22:34:23
  • 454KB xuruanshun 2019-11-20 19:37:48
  • u010349169 2018-09-26 15:42:50
  • long9870 2019-08-22 17:13:10
  • 479KB weixin_42110070 2021-01-29 17:39:14
  • qq_43792385 2020-02-25 10:39:48
  • weixin_39283212 2019-05-24 12:31:49
  • ciap37959 2019-07-18 17:00:00
  • CSDN__luo 2021-08-10 20:54:18
  • chengqiuming 2019-10-31 19:42:39
  • long9870 2019-10-17 16:02:55
  • studyvcmfc 2020-12-09 18:19:00
  • u013015681 2020-02-17 15:20:16

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 12,047
精华内容 4,818
关键字:

feign原理

友情链接: STM32F103C8T6八路ADC.rar