精华内容
下载资源
问答
  • Hystrix、Feign技术底层实现原理

    千次阅读 2020-05-26 11:55:30
    一.Feign的设计原理 1.1Feign是什么 Feign 的英文表意为“假装,伪装,变形”, 是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。...

    一. Feign的设计原理

    1.1 Feign是什么

           Feign 的英文表意为“假装,伪装,变形”, 是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。Feign被广泛应用在Spring Cloud 的解决方案中,是学习基于Spring Cloud 微服务架构不可或缺的重要组件。

    1.2 Feign解决了什么问题

           封装了Http调用流程,更适合面向接口化的变成习惯。在服务调用的场景中,我们经常调用基于Http协议的服务,而我们经常使用到的框架可能有HttpURLConnection、Apache HttpComponnets、OkHttp3 、Netty等等,这些框架在基于自身的专注点提供了自身特性。而从角色划分上来看,他们的职能是一致的提供Http调用服务。

    1.3 Feign是如何设计的

          Feign的远程调用基本流程,大致如图所示:

           

     PHASE 1. 基于面向接口的动态代理方式生成实现类

           在使用feign 时,会定义对应的接口类,在接口类上使用Http相关的注解,标识HTTP请求参数信息;在Feign 底层,通过基于面向接口的动态代理方式生成实现类,将请求调用委托到动态代理实现类,基本原理如下所示:

            

      PHASE 2. 根据Contract协议规则,解析接口类的注解信息,解析成内部表现

               

     

      默认Contract 实现

     Feign 默认有一套自己的协议规范,规定了一些注解,可以映射成对应的Http请求,如官方的一个例子:

    public interface GitHub {
      
      @RequestLine("GET /repos/{owner}/{repo}/contributors")
      List<Contributor> getContributors(@Param("owner") String owner, @Param("repo") String repository);
      
      class Contributor {
        String login;
        int contributions;
      }
    }
    
    
    

     上述的例子中,尝试调用GitHub.getContributors("foo","myrepo")的的时候,会转换成如下的HTTP请求:

    GET /repos/foo/myrepo/contributors
    HOST XXXX.XXX.XXX

    基于Spring MVC的协议规范SpringMvcContract:

    当前Spring Cloud 微服务解决方案中,为了降低学习成本,采用了Spring MVC的部分注解来完成 请求协议解析,也就是说 ,写客户端请求接口和像写服务端代码一样:客户端和服务端可以通过SDK的方式进行约定,客户端只需要引入服务端发布的SDK API,就可以使用面向接口的编码方式对接服务:

      

    当然,目前的Spring MVC的注解并不是可以完全使用的,有一些注解并不支持,如@GetMapping,@PutMapping 等,仅支持使用@RequestMapping 等,另外注解继承性方面也有些问题。

    PHASE 3. 基于 RequestBean,动态生成Request

    这一步很简单,就是根据传入的Bean对象和注解信息,从中提取出相应的值,来构造Http Request 对象。

    PHASE 4. 使用Encoder 将Bean转换成 Http报文正文(消息解析和转码逻辑)

    Feign 最终会将请求转换成Http 消息发送出去,传入的请求对象最终会解析成消息体,如下所示:   

     

    在接口定义上Feign做的比较简单,抽象出了Encoder 和decoder 接口:

    public interface Encoder {
      /** Type literal for {@code Map<String, ?>}, indicating the object to encode is a form. */
      Type MAP_STRING_WILDCARD = Util.MAP_STRING_WILDCARD;
    
      /** 
     * 将实体对象转换成Http请求的消息正文中 
     */
      void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;
    
      /** * Default implementation of {@code Encoder}. */
      class Default implements Encoder {
    
        @Override
        public void encode(Object object, Type bodyType, RequestTemplate template) {
          if (bodyType == String.class) {
            template.body(object.toString());
          } else if (bodyType == byte[].class) {
            template.body((byte[]) object, null);
          } else if (object != null) {
            throw new EncodeException(
                format("%s is not a type supported by this encoder.", object.getClass()));
          }
        }
      }
    }

     

    public interface Encoder {
      /** Type literal for {@code Map<String, ?>}, indicating the object to encode is a form. */
      Type MAP_STRING_WILDCARD = Util.MAP_STRING_WILDCARD;
    
      /** 
     * 将实体对象转换成Http请求的消息正文中 
     */
      void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;
    
      /** * Default implementation of {@code Encoder}. */
      class Default implements Encoder {
    
        @Override
        public void encode(Object object, Type bodyType, RequestTemplate template) {
          if (bodyType == String.class) {
            template.body(object.toString());
          } else if (bodyType == byte[].class) {
            template.body((byte[]) object, null);
          } else if (object != null) {
            throw new EncodeException(
                format("%s is not a type supported by this encoder.", object.getClass()));
          }
        }
      }
    }

    目前Feign 有以下实现:

    Encoder/ Decoder 实现

    说明

    JacksonEncoder,JacksonDecoder基于 Jackson 格式的持久化转换协议
    GsonEncoder,GsonDecoder基于Google GSON 格式的持久化转换协议
    SaxEncoder,SaxDecoder基于XML 格式的Sax 库持久化转换协议
    JAXBEncoder,JAXBDecoder基于XML 格式的JAXB 库持久化转换协议
    ResponseEntityEncoder,ResponseEntityDecoderSpring MVC 基于 ResponseEntity< T > 返回格式的转换协议
    SpringEncoder,SpringDecoder基于Spring MVC HttpMessageConverters 一套机制实现的转换协议 ,应用于Spring Cloud 体系中

    PHASE 5. 拦截器负责对请求和返回进行装饰处理

    在请求转换的过程中,Feign 抽象出来了拦截器接口,用于用户自定义对请求的操作:

    public interface RequestInterceptor {
    
      /** 
     * 可以在构造RequestTemplate 请求时,增加或者修改Header, Method, Body 等信息
     */
      void apply(RequestTemplate template);
    }

    比如,如果希望Http消息传递过程中被压缩,可以定义一个请求拦截器:

    public class FeignAcceptGzipEncodingInterceptor extends BaseRequestInterceptor {
    
        protected FeignAcceptGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
            super(properties);
        }
    
        /** 
     * {@inheritDoc}
     */
        @Override
        public void apply(RequestTemplate template) {
            // 在Header 头部添加相应的数据信息
            addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING,
                    HttpEncoding.DEFLATE_ENCODING);
        }
    }


    PHASE 6. 日志记录

    在发送和接收请求的时候,Feign定义了统一的日志门面来输出日志信息 , 并且将日志的输出定义了四个等级:

    级别

    说明

    NONE不做任何记录
    BASIC只记录输出Http 方法名称、请求URL、返回状态码和执行时间
    HEADERS记录输出Http 方法名称、请求URL、返回状态码和执行时间 和 Header 信息
    FULL记录Request 和Response的Header,Body和一些请求元数据

    PHASE 7 . 基于重试器发送HTTP请求

    Feign 内置了一个重试器,当HTTP请求出现IO异常时,Feign会有一个最大尝试次数发送请求,重试器有如下几个控制参数:

    重试参数

    说明

    默认值

    period初始重试时间间隔,当请求失败后,重试器将会暂停 初始时间间隔(线程 sleep 的方式)后再开始,避免强刷请求,浪费性能100ms
    maxPeriod当请求连续失败时,重试的时间间隔将按照:long interval = (long) (period * Math.pow(1.5, attempt - 1)); 计算,按照等比例方式延长,但是最大间隔时间为 maxPeriod, 设置此值能够避免 重试次数过多的情况下执行周期太长1000ms
    maxAttempts最大重试次数5

    以下是Feign重试的核心代码逻辑:

    final class SynchronousMethodHandler implements MethodHandler {
    
      @Override
      public Object invoke(Object[] argv) throws Throwable {
       //根据输入参数,构造Http 请求。
        RequestTemplate template = buildTemplateFromArgs.create(argv);
        // 克隆出一份重试器
        Retryer retryer = this.retryer.clone();
        // 尝试最大次数,如果中间有结果,直接返回
        while (true) {
          try {
            return executeAndDecode(template);
          } catch (RetryableException e) {
            retryer.continueOrPropagate(e);
            if (logLevel != Logger.Level.NONE) {
              logger.logRetry(metadata.configKey(), logLevel);
            }
            continue;
          }
        }
      }

    PHASE 8. 发送Http请求

    Feign 真正发送HTTP请求是委托给 feign.Client 来做的:

    public interface Client {
    
      /** 
     * 执行Http请求,并返回Response * @param request safe to replay.  
     */
      Response execute(Request request, Options options) throws IOException;
      }

    Feign 默认底层通过JDK 的 java.net.HttpURLConnection 实现了feign.Client接口类,在每次发送请求的时候,都会创建新的HttpURLConnection 链接,这也就是为什么默认情况下Feign的性能很差的原因。可以通过拓展该接口,使用Apache HttpClient 或者OkHttp3等基于连接池的高性能Http客户端,目前我们荐彩等项目内部使用的就是OkHttp3作为Http 客户端。

     

    二: Hystrix的设计原理

           Hystrix的三大设计原则:资源隔离、熔断器和命令模式。

    2.1 资源隔离

         在一个高度服务化的系统中,我们实现的一个业务逻辑通常会依赖多个服务,比如: 商品详情展示服务会依赖商品服务, 价格服 务, 商品评论服务. 如图所示:

          

            Hystrix通过将每个依赖服务分配独立的线程池进行资源隔离, 从而避免服务雪崩. 如下图所示, 当商品评论服务不可用时, 即使商品服务独立分配的20个线程全部处于同步等待状态,也不会影响其他依赖服务 的调用.

          

          线程池隔离的几点好处:

     

    • 使用超时返回的机制,避免同步调用服务时,调用时间过长,无法释放,导致资源耗尽的情况
    • 服务方可以控制请求数量,请求过多,可以直接拒绝,达到快速失败的目的;
    • 请求排队,线程池可以维护执行队列,将请求压到队列中处理

     

           但是使用线程池隔离也有一些弊端, 线程池隔离模式,会根据服务划分出独立的线程池,系统资源的线程并发数是有限的,当线程数过多,系统话费大量的CPU时间来做线程上下文切换的无用操作,反而降低系统性能;如果线程池隔离的过多,会导致真正用于接收用户请求的线程就相应地减少,系统吞吐量反而下降;在实践上,应当对像远程方法调用,网络资源请求这种服务时间不太可控的场景下使用线程池隔离模式处理。

    2.2  熔断器模式

    熔断器模式定义了熔断器开关相互转换的逻辑:

     

    服务的健康状况 = 请求失败数 / 请求总数. 熔断器开关由关闭到打开的状态转换是通过当前服务健康状况和设定阈值比较决定的.
    当熔断器开关关闭时, 请求被允许通过熔断器. 如果当前健康状况高于设定阈值, 开关继续保持关闭. 如果当前健康状况低于设定阈值, 开关则切换为打开状态.
    当熔断器开关打开时, 请求被禁止通过.
    当熔断器开关处于打开状态, 经过一段时间后, 熔断器会自动进入半开状态, 这时熔断器只允许一个请求通过. 当该请求调用成功时, 熔断器恢复到关闭状态. 若该请求失败, 熔断器继续保持打开状态, 接下来的请求被禁止通过.熔断器的开关能保证服务调用者在调用异常服务时, 快速返回结果, 避免大量的同步等待. 并且熔断器能在一段时间后继续侦测请求执行结果, 提供恢复服务调用的可能.

    2.3 命令模式

     

            Hystrix使用命令模式(继承HystrixCommand类或者是HystrixObservableCommand类)来包裹具体的服务调用逻辑(run方法), 并在命令模式中添加了服务调用失败后的降级逻辑(getFallback).
    同时我们在Command的构造方法中可以定义当前服务线程池和熔断器的相关参数. 如下代码所示:

    public class Service1HystrixCommand extends HystrixCommand<Response> {
    private Service1 service;
    private Request request;

    public Service1HystrixCommand(Service1 service, Request request){
    supper(
    Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ServiceGroup"))
    .andCommandKey(HystrixCommandKey.Factory.asKey("servcie1query"))
    .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("service1ThreadPool"))
    .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
    .withCoreSize(20))//服务线程池数量
    .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
    .withCircuitBreakerErrorThresholdPercentage(60)//熔断器关闭到打开阈值
    .withCircuitBreakerSleepWindowInMilliseconds(3000)//熔断器打开到关闭的时间窗长度
    ))
    this.service = service;
    this.request = request;
    );
    }

    @Override
    protected Response run(){
    return service1.call(request);
    }

    @Override
    protected Response getFallback(){
    return Response.dummy();
    }
    }

    在使用了Command模式构建了服务对象之后, 服务便拥有了熔断器和线程池的功能.

     

    2.4 Hystrix的内部处理逻辑

     下图为Hystrix服务调用的内部逻辑:

     

    1.构建Hystrix的Command对象, 调用执行方法.

    2.Hystrix检查当前服务的熔断器开关是否开启, 若开启, 则执行降级服务getFallback方法.

    3.若熔断器开关关闭, 则Hystrix检查当前服务的线程池是否能接收新的请求, 若超过线程池已满, 则执行降级服务getFallback方法.

    4.若线程池接受请求, 则Hystrix开始执行服务调用具体逻辑run方法.

    5.若服务执行失败, 则执行降级服务getFallback方法, 并将执行结果上报Metrics更新服务健康状况.

    6.若服务执行超时, 则执行降级服务getFallback方法, 并将执行结果上报Metrics更新服务健康状况.

    7.若服务执行成功, 返回正常结果.

    8.若服务降级方法getFallback执行成功, 则返回降级结果.

    9.若服务降级方法getFallback执行失败, 则抛出异常.

    2.5 Spring Cloud 下 Hystrix使用要注意的问题

    • Hystrix配置无法动态调节生效。Hystrix框架本身是使用的Archaius框架完成的配置加载和刷新,但是集成自 Spring Cloud下,无法有效地根据实时监控结果,动态调整熔断和系统参数
    • 线程池和Command之间的配置比较复杂,在Spring Cloud在做feigin-hystrix集成的时候,还有些BUG,对command的默认配置没有处理好,导致所有command占用公共的command线程池,没有细粒度控制,还需要做框架适配调整
    展开全文
  • Spring Cloud-Feign设计原理

    万次阅读 多人点赞 2018-09-26 15:42:50
    什么是FeignFeign 是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数...

    什么是Feign?

    Feign 的英文表意为“假装,伪装,变形”, 是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。
    Feign被广泛应用在Spring Cloud 的解决方案中,是学习基于Spring Cloud 微服务架构不可或缺的重要组件。
    开源项目地址:
    https://github.com/OpenFeign/feign

    Feign解决了什么问题?

    封装了Http调用流程,更适合面向接口化的变成习惯
    在服务调用的场景中,我们经常调用基于Http协议的服务,而我们经常使用到的框架可能有HttpURLConnection、Apache HttpComponnets、OkHttp3 、Netty等等,这些框架在基于自身的专注点提供了自身特性。而从角色划分上来看,他们的职能是一致的提供Http调用服务。具体流程如下:

    调用方 Client框架 服务方 构造Http请求URL 填写Http请求头信息 填写消息报文信息 发送Http请求 处理请求,返回结 返回报文 提取报文信息,转换成对应的Java bean 根据Bean中 的定义,业务处理 调用方 Client框架 服务方

    Feign是如何设计的?

    在这里插入图片描述

    PHASE 1. 基于面向接口的动态代理方式生成实现类

    在使用feign 时,会定义对应的接口类,在接口类上使用Http相关的注解,标识HTTP请求参数信息,如下所示:

    interface GitHub {
      @RequestLine("GET /repos/{owner}/{repo}/contributors")
      List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
    }
    
    public static class Contributor {
      String login;
      int contributions;
    }
    
    public class MyApp {
      public static void main(String... args) {
        GitHub github = Feign.builder()
                             .decoder(new GsonDecoder())
                             .target(GitHub.class, "https://api.github.com");
      
        // Fetch and print a list of the contributors to this library.
        List<Contributor> contributors = github.contributors("OpenFeign", "feign");
        for (Contributor contributor : contributors) {
          System.out.println(contributor.login + " (" + contributor.contributions + ")");
        }
      }
    }
    

    在Feign 底层,通过基于面向接口的动态代理方式生成实现类,将请求调用委托到动态代理实现类,基本原理如下所示:
    在这里插入图片描述

     public class ReflectiveFeign extends Feign{
      ///省略部分代码
      @Override
      public <T> T newInstance(Target<T> target) {
        //根据接口类和Contract协议解析方式,解析接口类上的方法和注解,转换成内部的MethodHandler处理方式
        Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
        Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<Method, MethodHandler>();
        List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<DefaultMethodHandler>();
    
        for (Method method : target.type().getMethods()) {
          if (method.getDeclaringClass() == Object.class) {
            continue;
          } else if(Util.isDefault(method)) {
            DefaultMethodHandler handler = new DefaultMethodHandler(method);
            defaultMethodHandlers.add(handler);
            methodToHandler.put(method, handler);
          } else {
            methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
          }
        }
        InvocationHandler handler = factory.create(target, methodToHandler);
        // 基于Proxy.newProxyInstance 为接口类创建动态实现,将所有的请求转换给InvocationHandler 处理。
        T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
    
        for(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
          defaultMethodHandler.bindTo(proxy);
        }
        return proxy;
      }
      //省略部分代码
    

    PHASE 2. 根据Contract协议规则,解析接口类的注解信息,解析成内部表现:

    在这里插入图片描述

    Feign 定义了转换协议,定义如下:

    /**
     * Defines what annotations and values are valid on interfaces.
     */
    public interface Contract {
    
      /**
       * Called to parse the methods in the class that are linked to HTTP requests.
       * 传入接口定义,解析成相应的方法内部元数据表示
       * @param targetType {@link feign.Target#type() type} of the Feign interface.
       */
      // TODO: break this and correct spelling at some point
      List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType);
    }
    
    默认Contract 实现

    Feign 默认有一套自己的协议规范,规定了一些注解,可以映射成对应的Http请求,如官方的一个例子:

    public interface GitHub {
      
      @RequestLine("GET /repos/{owner}/{repo}/contributors")
      List<Contributor> getContributors(@Param("owner") String owner, @Param("repo") String repository);
      
      class Contributor {
        String login;
        int contributions;
      }
    }
    
    

    上述的例子中,尝试调用GitHub.getContributors(“foo”,“myrepo”)的的时候,会转换成如下的HTTP请求:

    GET /repos/foo/myrepo/contributors
    HOST XXXX.XXX.XXX
    

    Feign 默认的协议规范

    注解接口Target使用说明
    @RequestLine方法上定义HttpMethod 和 UriTemplate. UriTemplate 中使用{} 包裹的表达式,可以通过在方法参数上使用@Param 自动注入
    @Param方法参数定义模板变量,模板变量的值可以使用名称的方式使用模板注入解析
    @Headers类上或者方法上定义头部模板变量,使用@Param 注解提供参数值的注入。如果该注解添加在接口类上,则所有的请求都会携带对应的Header信息;如果在方法上,则只会添加到对应的方法请求上
    @QueryMap方法上定义一个键值对或者 pojo,参数值将会被转换成URL上的 query 字符串上
    @HeaderMap方法上定义一个HeaderMap, 与 UrlTemplate 和HeaderTemplate 类型,可以使用@Param 注解提供参数值

    具体FeignContract 是如何解析的,不在本文的介绍范围内,详情请参考代码:
    https://github.com/OpenFeign/feign/blob/master/core/src/main/java/feign/Contract.java

    基于Spring MVC的协议规范SpringMvcContract:

    当前Spring Cloud 微服务解决方案中,为了降低学习成本,采用了Spring MVC的部分注解来完成 请求协议解析,也就是说 ,写客户端请求接口和像写服务端代码一样:客户端和服务端可以通过SDK的方式进行约定,客户端只需要引入服务端发布的SDK API,就可以使用面向接口的编码方式对接服务:
    在这里插入图片描述

    我们团队内部就是按照这种思路,结合Spring Boot Starter 的特性,定义了服务端starter,
    服务消费者在使用的时候,只需要引入Starter,就可以调用服务。这个比较适合平台无关性,接口抽象出来的好处就是可以根据服务调用实现方式自有切换:

    1. 可以基于简单的Http服务调用;
    2. 可以基于Spring Cloud 微服务架构调用;
    3. 可以基于Dubbo SOA服务治理

    这种模式比较适合在SaSS混合软件服务的模式下自有切换,根据客户的硬件能力选择合适的方式部署,也可以基于自身的服务集群部署微服务

    至于Spring Cloud 是如何实现 协议解析的,可参考代码:
    https://github.com/spring-cloud/spring-cloud-openfeign/blob/master/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java

    当然,目前的Spring MVC的注解并不是可以完全使用的,有一些注解并不支持,如@GetMapping,@PutMapping 等,仅支持使用@RequestMapping 等,另外注解继承性方面也有些问题;具体限制细节,每个版本能会有些出入,可以参考上述的代码实现,比较简单。

    Spring Cloud 没有基于Spring MVC 全部注解来做Feign 客户端注解协议解析,个人认为这个是一个不小的坑。在刚入手Spring Cloud 的时候,就碰到这个问题。后来是深入代码才解决的… 这个应该有人写了增强类来处理,暂且不表,先MARK一下,是一个开源代码练手的好机会。

    PHASE 3. 基于 RequestBean,动态生成Request

    根据传入的Bean对象和注解信息,从中提取出相应的值,来构造Http Request 对象:
    在这里插入图片描述

    PHASE 4. 使用Encoder 将Bean转换成 Http报文正文(消息解析和转码逻辑)

    Feign 最终会将请求转换成Http 消息发送出去,传入的请求对象最终会解析成消息体,如下所示:
    在这里插入图片描述

    在接口定义上Feign做的比较简单,抽象出了Encoder 和decoder 接口:

    public interface Encoder {
      /** Type literal for {@code Map<String, ?>}, indicating the object to encode is a form. */
      Type MAP_STRING_WILDCARD = Util.MAP_STRING_WILDCARD;
    
      /**
       * Converts objects to an appropriate representation in the template.
       *  将实体对象转换成Http请求的消息正文中
       * @param object   what to encode as the request body.
       * @param bodyType the type the object should be encoded as. {@link #MAP_STRING_WILDCARD}
       *                 indicates form encoding.
       * @param template the request template to populate.
       * @throws EncodeException when encoding failed due to a checked exception.
       */
      void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;
    
      /**
       * Default implementation of {@code Encoder}.
       */
      class Default implements Encoder {
    
        @Override
        public void encode(Object object, Type bodyType, RequestTemplate template) {
          if (bodyType == String.class) {
            template.body(object.toString());
          } else if (bodyType == byte[].class) {
            template.body((byte[]) object, null);
          } else if (object != null) {
            throw new EncodeException(
                format("%s is not a type supported by this encoder.", object.getClass()));
          }
        }
      }
    }
    
    public interface Decoder {
    
      /**
       * Decodes an http response into an object corresponding to its {@link
       * java.lang.reflect.Method#getGenericReturnType() generic return type}. If you need to wrap
       * exceptions, please do so via {@link DecodeException}.
       *  从Response 中提取Http消息正文,通过接口类声明的返回类型,消息自动装配
       * @param response the response to decode 
       * @param type     {@link java.lang.reflect.Method#getGenericReturnType() generic return type} of
       *                 the method corresponding to this {@code response}.
       * @return instance of {@code type}
       * @throws IOException     will be propagated safely to the caller.
       * @throws DecodeException when decoding failed due to a checked exception besides IOException.
       * @throws FeignException  when decoding succeeds, but conveys the operation failed.
       */
      Object decode(Response response, Type type) throws IOException, DecodeException, FeignException;
    
      /** Default implementation of {@code Decoder}. */
      public class Default extends StringDecoder {
    
        @Override
        public Object decode(Response response, Type type) throws IOException {
          if (response.status() == 404) return Util.emptyValueOf(type);
          if (response.body() == null) return null;
          if (byte[].class.equals(type)) {
            return Util.toByteArray(response.body().asInputStream());
          }
          return super.decode(response, type);
        }
      }
    }
    
    

    目前Feign 有以下实现:

    Encoder/ Decoder 实现说明
    JacksonEncoder,JacksonDecoder基于 Jackson 格式的持久化转换协议
    GsonEncoder,GsonDecoder基于Google GSON 格式的持久化转换协议
    SaxEncoder,SaxDecoder基于XML 格式的Sax 库持久化转换协议
    JAXBEncoder,JAXBDecoder基于XML 格式的JAXB 库持久化转换协议
    ResponseEntityEncoder,ResponseEntityDecoderSpring MVC 基于 ResponseEntity< T > 返回格式的转换协议
    SpringEncoder,SpringDecoder基于Spring MVC HttpMessageConverters 一套机制实现的转换协议 ,应用于Spring Cloud 体系中
    PHASE 5. 拦截器负责对请求和返回进行装饰处理

    在请求转换的过程中,Feign 抽象出来了拦截器接口,用于用户自定义对请求的操作:

    public interface RequestInterceptor {
    
      /**
       * 可以在构造RequestTemplate 请求时,增加或者修改Header, Method, Body 等信息
       * Called for every request. Add data using methods on the supplied {@link RequestTemplate}.
       */
      void apply(RequestTemplate template);
    }
    

    比如,如果希望Http消息传递过程中被压缩,可以定义一个请求拦截器:

    public class FeignAcceptGzipEncodingInterceptor extends BaseRequestInterceptor {
    
    	/**
    	 * Creates new instance of {@link FeignAcceptGzipEncodingInterceptor}.
    	 *
    	 * @param properties the encoding properties
    	 */
    	protected FeignAcceptGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
    		super(properties);
    	}
    
    	/**
    	 * {@inheritDoc}
    	 */
    	@Override
    	public void apply(RequestTemplate template) {
    		//  在Header 头部添加相应的数据信息
    		addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING,
    				HttpEncoding.DEFLATE_ENCODING);
    	}
    }
    
    PHASE 6. 日志记录

    在发送和接收请求的时候,Feign定义了统一的日志门面来输出日志信息 , 并且将日志的输出定义了四个等级:

    级别说明
    NONE不做任何记录
    BASIC只记录输出Http 方法名称、请求URL、返回状态码和执行时间
    HEADERS记录输出Http 方法名称、请求URL、返回状态码和执行时间 和 Header 信息
    FULL记录Request 和Response的Header,Body和一些请求元数据
    public abstract class Logger {
    
      protected static String methodTag(String configKey) {
        return new StringBuilder().append('[').append(configKey.substring(0, configKey.indexOf('(')))
            .append("] ").toString();
      }
    
      /**
       * Override to log requests and responses using your own implementation. Messages will be http
       * request and response text.
       *
       * @param configKey value of {@link Feign#configKey(Class, java.lang.reflect.Method)}
       * @param format    {@link java.util.Formatter format string}
       * @param args      arguments applied to {@code format}
       */
      protected abstract void log(String configKey, String format, Object... args);
    
      protected void logRequest(String configKey, Level logLevel, Request request) {
        log(configKey, "---> %s %s HTTP/1.1", request.method(), request.url());
        if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {
    
          for (String field : request.headers().keySet()) {
            for (String value : valuesOrEmpty(request.headers(), field)) {
              log(configKey, "%s: %s", field, value);
            }
          }
    
          int bodyLength = 0;
          if (request.body() != null) {
            bodyLength = request.body().length;
            if (logLevel.ordinal() >= Level.FULL.ordinal()) {
              String
                  bodyText =
                  request.charset() != null ? new String(request.body(), request.charset()) : null;
              log(configKey, ""); // CRLF
              log(configKey, "%s", bodyText != null ? bodyText : "Binary data");
            }
          }
          log(configKey, "---> END HTTP (%s-byte body)", bodyLength);
        }
      }
    
      protected void logRetry(String configKey, Level logLevel) {
        log(configKey, "---> RETRYING");
      }
    
      protected Response logAndRebufferResponse(String configKey, Level logLevel, Response response,
                                                long elapsedTime) throws IOException {
        String reason = response.reason() != null && logLevel.compareTo(Level.NONE) > 0 ?
            " " + response.reason() : "";
        int status = response.status();
        log(configKey, "<--- HTTP/1.1 %s%s (%sms)", status, reason, elapsedTime);
        if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {
    
          for (String field : response.headers().keySet()) {
            for (String value : valuesOrEmpty(response.headers(), field)) {
              log(configKey, "%s: %s", field, value);
            }
          }
    
          int bodyLength = 0;
          if (response.body() != null && !(status == 204 || status == 205)) {
            // HTTP 204 No Content "...response MUST NOT include a message-body"
            // HTTP 205 Reset Content "...response MUST NOT include an entity"
            if (logLevel.ordinal() >= Level.FULL.ordinal()) {
              log(configKey, ""); // CRLF
            }
            byte[] bodyData = Util.toByteArray(response.body().asInputStream());
            bodyLength = bodyData.length;
            if (logLevel.ordinal() >= Level.FULL.ordinal() && bodyLength > 0) {
              log(configKey, "%s", decodeOrDefault(bodyData, UTF_8, "Binary data"));
            }
            log(configKey, "<--- END HTTP (%s-byte body)", bodyLength);
            return response.toBuilder().body(bodyData).build();
          } else {
            log(configKey, "<--- END HTTP (%s-byte body)", bodyLength);
          }
        }
        return response;
      }
    
      protected IOException logIOException(String configKey, Level logLevel, IOException ioe, long elapsedTime) {
        log(configKey, "<--- ERROR %s: %s (%sms)", ioe.getClass().getSimpleName(), ioe.getMessage(),
            elapsedTime);
        if (logLevel.ordinal() >= Level.FULL.ordinal()) {
          StringWriter sw = new StringWriter();
          ioe.printStackTrace(new PrintWriter(sw));
          log(configKey, sw.toString());
          log(configKey, "<--- END ERROR");
        }
        return ioe;
      }
    
    PHASE 7 . 基于重试器发送HTTP请求

    Feign 内置了一个重试器,当HTTP请求出现IO异常时,Feign会有一个最大尝试次数发送请求,以下是Feign核心
    代码逻辑:

    final class SynchronousMethodHandler implements MethodHandler {
    
      // 省略部分代码
    
      @Override
      public Object invoke(Object[] argv) throws Throwable {
       //根据输入参数,构造Http 请求。
        RequestTemplate template = buildTemplateFromArgs.create(argv);
        // 克隆出一份重试器
        Retryer retryer = this.retryer.clone();
        // 尝试最大次数,如果中间有结果,直接返回
        while (true) {
          try {
            return executeAndDecode(template);
          } catch (RetryableException e) {
            retryer.continueOrPropagate(e);
            if (logLevel != Logger.Level.NONE) {
              logger.logRetry(metadata.configKey(), logLevel);
            }
            continue;
          }
        }
      }
    

    重试器有如下几个控制参数:

    重试参数说明默认值
    period初始重试时间间隔,当请求失败后,重试器将会暂停 初始时间间隔(线程 sleep 的方式)后再开始,避免强刷请求,浪费性能100ms
    maxPeriod当请求连续失败时,重试的时间间隔将按照:long interval = (long) (period * Math.pow(1.5, attempt - 1)); 计算,按照等比例方式延长,但是最大间隔时间为 maxPeriod, 设置此值能够避免 重试次数过多的情况下执行周期太长1000ms
    maxAttempts最大重试次数5

    具体的代码实现可参考:
    https://github.com/OpenFeign/feign/blob/master/core/src/main/java/feign/Retryer.java

    PHASE 8. 发送Http请求

    Feign 真正发送HTTP请求是委托给 feign.Client 来做的:

    public interface Client {
    
      /**
       * Executes a request against its {@link Request#url() url} and returns a response.
       *  执行Http请求,并返回Response
       * @param request safe to replay.
       * @param options options to apply to this request.
       * @return connected response, {@link Response.Body} is absent or unread.
       * @throws IOException on a network error connecting to {@link Request#url()}.
       */
      Response execute(Request request, Options options) throws IOException;
      }
    

    Feign 默认底层通过JDK 的 java.net.HttpURLConnection 实现了feign.Client接口类,在每次发送请求的时候,都会创建新的HttpURLConnection 链接,这也就是为什么默认情况下Feign的性能很差的原因。可以通过拓展该接口,使用Apache HttpClient 或者OkHttp3等基于连接池的高性能Http客户端,我们项目内部使用的就是OkHttp3作为Http 客户端。

    如下是Feign 的默认实现,供参考:

    public static class Default implements Client {
    
        private final SSLSocketFactory sslContextFactory;
        private final HostnameVerifier hostnameVerifier;
    
        /**
         * Null parameters imply platform defaults.
         */
        public Default(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVerifier) {
          this.sslContextFactory = sslContextFactory;
          this.hostnameVerifier = hostnameVerifier;
        }
    
        @Override
        public Response execute(Request request, Options options) throws IOException {
          HttpURLConnection connection = convertAndSend(request, options);
          return convertResponse(connection).toBuilder().request(request).build();
        }
    
        HttpURLConnection convertAndSend(Request request, Options options) throws IOException {
          final HttpURLConnection
              connection =
              (HttpURLConnection) new URL(request.url()).openConnection();
          if (connection instanceof HttpsURLConnection) {
            HttpsURLConnection sslCon = (HttpsURLConnection) connection;
            if (sslContextFactory != null) {
              sslCon.setSSLSocketFactory(sslContextFactory);
            }
            if (hostnameVerifier != null) {
              sslCon.setHostnameVerifier(hostnameVerifier);
            }
          }
          connection.setConnectTimeout(options.connectTimeoutMillis());
          connection.setReadTimeout(options.readTimeoutMillis());
          connection.setAllowUserInteraction(false);
          connection.setInstanceFollowRedirects(true);
          connection.setRequestMethod(request.method());
    
          Collection<String> contentEncodingValues = request.headers().get(CONTENT_ENCODING);
          boolean
              gzipEncodedRequest =
              contentEncodingValues != null && contentEncodingValues.contains(ENCODING_GZIP);
          boolean
              deflateEncodedRequest =
              contentEncodingValues != null && contentEncodingValues.contains(ENCODING_DEFLATE);
    
          boolean hasAcceptHeader = false;
          Integer contentLength = null;
          for (String field : request.headers().keySet()) {
            if (field.equalsIgnoreCase("Accept")) {
              hasAcceptHeader = true;
            }
            for (String value : request.headers().get(field)) {
              if (field.equals(CONTENT_LENGTH)) {
                if (!gzipEncodedRequest && !deflateEncodedRequest) {
                  contentLength = Integer.valueOf(value);
                  connection.addRequestProperty(field, value);
                }
              } else {
                connection.addRequestProperty(field, value);
              }
            }
          }
          // Some servers choke on the default accept string.
          if (!hasAcceptHeader) {
            connection.addRequestProperty("Accept", "*/*");
          }
    
          if (request.body() != null) {
            if (contentLength != null) {
              connection.setFixedLengthStreamingMode(contentLength);
            } else {
              connection.setChunkedStreamingMode(8196);
            }
            connection.setDoOutput(true);
            OutputStream out = connection.getOutputStream();
            if (gzipEncodedRequest) {
              out = new GZIPOutputStream(out);
            } else if (deflateEncodedRequest) {
              out = new DeflaterOutputStream(out);
            }
            try {
              out.write(request.body());
            } finally {
              try {
                out.close();
              } catch (IOException suppressed) { // NOPMD
              }
            }
          }
          return connection;
        }
    
        Response convertResponse(HttpURLConnection connection) throws IOException {
          int status = connection.getResponseCode();
          String reason = connection.getResponseMessage();
    
          if (status < 0) {
            throw new IOException(format("Invalid status(%s) executing %s %s", status,
                connection.getRequestMethod(), connection.getURL()));
          }
    
          Map<String, Collection<String>> headers = new LinkedHashMap<String, Collection<String>>();
          for (Map.Entry<String, List<String>> field : connection.getHeaderFields().entrySet()) {
            // response message
            if (field.getKey() != null) {
              headers.put(field.getKey(), field.getValue());
            }
          }
    
          Integer length = connection.getContentLength();
          if (length == -1) {
            length = null;
          }
          InputStream stream;
          if (status >= 400) {
            stream = connection.getErrorStream();
          } else {
            stream = connection.getInputStream();
          }
          return Response.builder()
                  .status(status)
                  .reason(reason)
                  .headers(headers)
                  .body(stream, length)
                  .build();
        }
      }
    

    Feign 的性能怎么样?

    Feign 整体框架非常小巧,在处理请求转换和消息解析的过程中,基本上没什么时间消耗。真正影响性能的,是处理Http请求的环节。
    如上所述,由于默认情况下,Feign采用的是JDK的HttpURLConnection,所以整体性能并不高,刚开始接触Spring Cloud 的同学,如果没注意这些细节,可能会对Spring Cloud 有很大的偏见。
    我们项目内部使用的是OkHttp3 作为连接客户端。
    系统的压测方案后续在贴出来,有兴趣的同学可以持续关注~

    另外作者已开通微信订阅号,精品文章同步更新,欢迎关注~
    亦山札记公众号

    展开全文
  • Feign原理 (图解)

    2021-03-04 22:53:02
    疯狂创客圈 高并发 总目录 】 1 SpringCloud 中 Feign 核心原理 如果不了解 SpringCloud 中 Feign 核心原理,不会真正的了解 SpringCloud 的性能优化和配置优化,也就不可能做到真正掌握 SpringCloud。 本章从Feign...

    文章很长,建议收藏起来,慢慢读! 高并发学习社群 - 疯狂创客圈奉献给大家:


    推荐2:史上最全 Java 面试题 21 个专题

    史上最全 Java 面试题 21 个专题阿里、京东、美团、头条.... 随意挑、横着走!!!
    Java基础 
    1: JVM面试题(史上最强、持续更新、吐血推荐)https://www.cnblogs.com/crazymakercircle/p/14365820.html
    2:Java基础面试题(史上最全、持续更新、吐血推荐)https://www.cnblogs.com/crazymakercircle/p/14366081.html
    3:死锁面试题(史上最强、持续更新)[https://www.cnblogs.com/crazymakercircle/p/14323919.html]
    4:设计模式面试题 (史上最全、持续更新、吐血推荐)https://www.cnblogs.com/crazymakercircle/p/14367101.html
    5:架构设计面试题 (史上最全、持续更新、吐血推荐)https://www.cnblogs.com/crazymakercircle/p/14367907.html
    还有 10 + 篇必刷、必刷 的面试题更多 ....., 请参见【 疯狂创客圈 高并发 总目录 】

    推荐3: 疯狂创客圈 springCloud 高并发系列

    springCloud 高质量 博文 
    nacos 实战(史上最全)sentinel (史上最全+入门教程)
    springcloud + webflux 高并发实战Webflux(史上最全)
    SpringCloud gateway (史上最全) 
    还有 10 + 篇 必刷、必刷 的高质量 博文更多 ....., 请参见【 疯狂创客圈 高并发 总目录 】

    1 SpringCloud 中 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 高并发研习社群 【博客园 总入口 】

    https://www.cnblogs.com/crazymakercircle/p/11965726.html

    展开全文
  • Feign的工作原理

    万次阅读 2019-05-04 12:48:03
    Feign的工作原理 主程序入口添加了@EnableFeignClients注解开启对FeignClient扫描加载处理。根据Feign Client的开发规范,定义接口并加@FeignClientd注解。 当程序启动时,回进行包扫描,扫描所有@FeignClients...

    Feign的工作原理

    • 主程序入口添加了@EnableFeignClients注解开启对FeignClient扫描加载处理。根据Feign Client的开发规范,定义接口并加@FeignClientd注解。

    • 当程序启动时,回进行包扫描,扫描所有@FeignClients的注解的类,并且讲这些信息注入Spring IOC容器中,当定义的的Feign接口中的方法呗调用时,通过JDK的代理方式,来生成具体的RequestTemplate.当生成代理时,Feign会为每个接口方法创建一个RequestTemplate。当生成代理时,Feign会为每个接口方法创建一个RequestTemplate对象,改对象封装可HTTP请求需要的全部信息,如请求参数名,请求方法等信息都是在这个过程中确定的。

    • 然后RequestTemplate生成Request,然后把Request交给Client去处理,这里指的时Client可以时JDK原生的URLConnection,Apache的HttpClient,也可以时OKhttp,最后Client被封装到LoadBalanceClient类,这个类结合Ribbon负载均衡发器服务之间的调用。

    Feign注解剖析

    @FeignClient注解主要被@Target({ElementType.TYPE})修饰,表示该注解主要使用在接口上。它具备了如下的属性:

    • name:指定FeignClient的名称,如果使用了Ribbon,name就作为微服务的名称,用于服务发现。

    • url:url一般用于调试,可以指定@FeignClient调用的地址。

    • decode404: 当发生404错误时,如果该字段为true,会调用decoder进行解码,否则抛出FeignException.

    • configuration:Feign配置类,可以自定或者配置Feign的Encoder,Decoder,LogLevel,Contract。

    • fallback:定义容错的处理类,当调用远程接口失败或者超时时,会调用对应的接口的容错逻辑,fallback指定的类必须实现@Feign标记的接口。

    • fallbacjFactory:工厂类,用于生成fallback类实例,通过这个属性可以实现每个接口通用的容错逻辑们介绍重复的代码。

    • path:定义当前FeignClient的统一前缀。

    Feign开启GZIP压缩

    Spring Cloud Feign支持对请求和响应的进行GZIP压缩,以提高通信效率。 在yml文件需要如下的配置

    feign:
      compression:
        request:
          enabled: true #开请求压缩
          mimeTypes: #媒体类型 text/xml,application/xml,application/json
          minRequestSize: 2048 #最小的请求大小
        response:
          enabled: true #开启响应的压缩
    复制代码

    需要注意的是,在采用了压缩之后,需要使用二级制的方式进行数据传递,所有返回值就需要使用 ResponseEntity<byte[]> 接收.

    @FeignClient(name = "github-client",url = "https://api.github.com",configuration = HelloFeignServiceConfig.class)
    public interface HelloFeignService {
    
        /*
        这个返回类型如果采用了压缩,那么就是二进制的方式,就需要使用ResponseEntity<byte[]>作为返回值
         */
        @RequestMapping(value = "/search/repositories",method = RequestMethod.GET)
        ResponseEntity<byte[]> searchRepositories(@RequestParam("q")String parameter);
    }
    复制代码

    Feign Client开启日志

    需要在注解类添加配置的类:

    @FeignClient(name = "github-client",url = "https://api.github.com",configuration = HelloFeignServiceConfig.class)
    复制代码

    注解类的代码如下:

    @Configuration
    public class HelloFeignServiceConfig {
    
        /**
         *
         * Logger.Level 的具体级别如下:
             NONE:不记录任何信息
             BASIC:仅记录请求方法、URL以及响应状态码和执行时间
             HEADERS:除了记录 BASIC级别的信息外,还会记录请求和响应的头信息
             FULL:记录所有请求与响应的明细,包括头信息、请求体、元数据
         * @return
         */
        @Bean
        Logger.Level feignLoggerLevel() {
            return Logger.Level.FULL;
        }
    
    }
    复制代码

    Feign的超时设置

    Feign的调用分两层,即Ribbon层的调用和Hystrix的调用,高版本的Hystrix默认是关闭的。

    • 如果出现上述的错误,那么就需要添加如下的配置

      #请求处理的超时时间 ribbon.ReadTimeout: 12000 #请求链接超时时间 ribbon.ConnectionTimeout: 30000

    • 如果开启了Hystrix,Hystrix的超时报错信息如下:

    此时可以添加如下配置:

    hystrix:
      command:
        default:
          circuitBreaker:
            sleepWindowInMilliseconds: 30000
            requestVolumeThreshold: 50
          execution:
            timeout:
              enabled: true
            isolation:
              strategy: SEMAPHORE
              semaphore:
                maxConcurrentRequests: 50
              thread:
                timeoutInMilliseconds: 100000
    复制代码

    Feign的Post和Get的多参数传递

    在SpringMVC中可以使用Post或者Get请求轻易的请求到对应的接口并且讲参数绑定到POJO,但是Feign并没有全部实现SpringMVC的功能,如果使用GET请求到接口,就无法将参数绑定到POJO,但是可以使用以下的几种方式实现相应的功能。

    • 将POJO内的字段作为一个个的字段卸载接口上

    • 将参数编程一个map

    • POJO使用@RequestBody修饰,但是这个违反Restful的原则

    以下的代码将Feign的请求拦截下来,将参数进行处理之后统一的编程map

    @Component
    public class FeignRequestInterceptor implements RequestInterceptor{
        @Autowired
        private ObjectMapper objectMapper;
    
    
    
        @Override
        public void apply(RequestTemplate requestTemplate) {
            // feign 不支持 GET 方法传 POJO, json body转query
            if (requestTemplate.method().equalsIgnoreCase("GET") && requestTemplate.body()!=null){
                try {
                    JsonNode jsonNode = objectMapper.readTree(requestTemplate.body());
                    requestTemplate.body(null);
                    Map<String,Collection<String>> queries = new HashMap<>();
                    buildQuery(jsonNode,"",queries);
                    requestTemplate.queries(queries);
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
            }
    
        }
        private void buildQuery(JsonNode jsonNode, String path, Map<String, Collection<String>> queries) {
            if (!jsonNode.isContainerNode()) {   // 叶子节点
                if (jsonNode.isNull()) {
                    return;
                }
                Collection<String> values = queries.get(path);
                if (null == values) {
                    values = new ArrayList<>();
                    queries.put(path, values);
                }
                values.add(jsonNode.asText());
                return;
            }
            if (jsonNode.isArray()) {   // 数组节点
                Iterator<JsonNode> it = jsonNode.elements();
                while (it.hasNext()) {
                    buildQuery(it.next(), path, queries);
                }
            } else {
                Iterator<Map.Entry<String, JsonNode>> it = jsonNode.fields();
                while (it.hasNext()) {
                    Map.Entry<String, JsonNode> entry = it.next();
                    if (StringUtils.hasText(path)) {
                        buildQuery(entry.getValue(), path + "." + entry.getKey(), queries);
                    } else {  // 根节点
                        buildQuery(entry.getValue(), entry.getKey(), queries);
                    }
                }
            }
        }
    
    }
    复制代码

    除了上面的方法之外还可以使用venus-cloud-feign这个依赖,该依赖实现了GET请求的参数Map包装,无需再去自己写。依赖如下:

    <!-- https://mvnrepository.com/artifact/cn.springcloud.feign/venus-cloud-feign-core -->
    <dependency>
        <groupId>cn.springcloud.feign</groupId>
        <artifactId>venus-cloud-feign-core</artifactId>
        <version>1.0.0</version>
    </dependency>
    复制代码

    github的地址如下:github.com/SpringCloud…

    转载于:https://juejin.im/post/5ccbe82851882544da5008ce

    展开全文
  • 实战系列-被面试官问到Feign原理

    千次阅读 2020-06-29 17:00:54
    registerFeignClients()方法 内部流转逻辑 FeignInvocationHandler 调用处理器 MethodHandler 方法处理器 SynchronousMethodHandler 同步处理 Feign 客户端组件 feign.Client Client.Default类 ApacheHttpClient类 ...
  • Feign实现原理

    千次阅读 2019-07-18 17:00:00
    什么是Feign? Feign是一个java的到http客户端绑定的开源项目。 Feign的主要目标是将Java Http 客户端变得简单。Feign的源码地址:...Feign的工作原理 feign是一个伪客户端,即它不做任何的...
  • Feign的工作原理(基础版)

    千次阅读 2020-04-28 18:20:57
    文章目录Feign的简单介绍Feign的工作原理1.创建远程接口的本地代理实例2.封装Request对象并进行编码3.`feign.Client`发送请求并对获取结果进行解码总结 Feign的简单介绍 Feign组件主要用于微服务项目中,用来简化...
  • 《深入理解Spring Cloud与微服务构建》学习笔记(十二)~写一个Feign客户端,可以直接运行。
  • Spring Cloud源码解析 什么是Feign feign 是一种声明式的web 客户端,可以使用它的注解创建接口,它也支持 ... feign底层是使用了ribbon作为负载均衡的客户端,而ribbon的负载均衡也是依赖于eureka 获得各个服...
  • Spring Cloud Feign相关原理分析

    千次阅读 2019-05-24 12:31:49
    1.认识Spring Cloud Feign 在使用Spring Cloud构建的微服务架构中,Feign用来提供声明式的服务客户端定义方式。 Feign是一个伪客户端(不做任何的请求处理),它通过处理注解生成request,以此来简化HTTP API的开发。 ...
  • 超详细的SpringCloud底层原理

    万次阅读 多人点赞 2019-10-03 15:21:40
    3、业务集群,这一层我有些项目是分两层的,就是上面加了一个负载层,下面是从service开始的,底层只是单纯的接口,controller是单独一层由feign实现,然后内部不同业务服务接口互调,直接调用controller层,只能说...
  • 1.Feign是什么? Feign是一个声明式的Web服务客户端,使得编写Web服务客户端变得非常容易, 只需要创建一个接口,然后在上面添加注解即可 2.Feign能干什么 Feign旨在使编写Java Http客户端变得更容易。 前面在使用...
  • feign-底层http请求组件剖析

    万次阅读 2017-06-15 09:48:14
    这篇文章主要介绍下feign底层使用的http发送请求的组件,下面就一步步得来做剖析。 我们知道使用feign的时候两个重要的注解@FeignClient和@EnableFeignClients,这两个注解分别是标识是一个feign的client,在启动的...
  • Feign工作原理详解(一)

    2020-11-09 21:11:48
    1. Feign 的工作流程 首先我们了解一下Feign的工作流程,以访问github中的openfeign的contributor信息为例 github中的API列表如下: https://api.github.com/ 我们将访问: repository_url: ...
  • SpringCloud FeignClient+Ribbon 底层实现原理(三) 说实话上一篇文章确实内容比较少,所用在开一篇文章彻底将Feign和Ribbon怎么结合的讲下在上一篇文章中我们讲到了jdk的动态代理,我们重点看下invocationHanlder类...
  • SpringCloud底层原理

    2021-02-24 22:20:58
    本文先从最核心的几个组件,也就是Eureka、Ribbon、Feign、Hystrix、Zuul入手,来剖析其底层的工作原理。先来给大家说一个业务场景,假设咱们现在开发一个电商网站,要实现支付订单的功能。流程如下:创建一个订单后...
  • 面试题推荐:100期面试题汇总 三、Spring Cloud核心组件:Feign Feign的关键机制是使用了动态代理 1)、首先,对某个接口定义了@FeignClient注解,Feign就会针对这个接口创建一个动态代理 2)、接着调用接口的时候,...
  • Feign使用原理

    2021-02-05 17:18:58
    Feign Client会在底层根据你的注解,跟你指定的服务建立连接、构造请求、发起靕求、获取响应、解析响应,等等。这一系列脏活累活,人家Feign全给你干了。 那么问题来了,Feign是如何做到这么神奇的呢?很简单,...
  • Feign 原理 (图解)

    千次阅读 2019-12-01 13:06:54
    Feign原理 (核心图解) 疯狂创客圈 Java 高并发【 亿级流量聊天室实战】实战系列 【博客园总入口 】 疯狂创客圈 正在进行分布式和高并发基础原理的研习,进行已经发布一些基础性的文章: 一、版本1 :springcloud...
  • feign中默认是包含了Ribbon相关jar包的 使用步骤: 1 @enable驱动@EnableFeignClients 2 标记接口 @FeignClient 3 注入接口实例(代理对象) 4 调用 源码分析: @Retention(RetentionPolicy.RUNTIME) @Target...
  • 前篇:Spring Cloud——Eureka注册中心原理及示例 官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.2.1.RELEASE/reference/html/#spring-cloud-ribbon 在前后端分离架构中,服务层被拆分...
  • Spring Cloud核心组件:Feign四. Spring Cloud核心组件:Ribbon五. Spring Cloud核心组件:Hystrix六. Spring Cloud核心组件:Zuul七. 总结 概述 毫无疑问,Spring Cloud是目前微服务架构领域的翘楚,无数的书籍...
  • Spring Cloud OpenFeign 工作原理解析

    万次阅读 多人点赞 2020-05-07 18:05:45
    使用方式 在介绍 OpenFeign 的工作原理之前, 首先值得说明的是使用了 Open Feign 后, 开发人员的效率是如何得到提升的。 下面展示在使用了 OpenFeign 之后, 一个接口的提供方和消费方是如何快速高效地完成代码...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,502
精华内容 1,800
关键字:

feign的底层原理