当前位置 主页 > 网站技术 > 代码类 >

    Spring Cloud Feign组件实例解析

    栏目:代码类 时间:2019-11-15 18:05

    这篇文章主要介绍了Spring Cloud Feign组件实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    采用Spring Cloud微服务框架后,经常会涉及到服务间调用,服务间调用采用了Feign组件。

    由于之前有使用dubbo经验。dubbo的负载均衡策略(轮训、最小连接数、随机轮训、加权轮训),dubbo失败策略(快速失败、失败重试等等),

    所以Feign负载均衡策略的是什么? 失败后是否会重试,重试策略又是什么?带这个疑问,查了一些资料,最后还是看了下代码。毕竟代码就是一切

    Spring boot集成Feign的大概流程:

    1、利用FeignAutoConfiguration自动配置。并根据EnableFeignClients 自动注册产生Feign的代理类。

    2、注册方式利用FeignClientFactoryBean,熟悉Spring知道FactoryBean 产生bean的工厂,有个重要方法getObject产生FeignClient容器bean

    3、同时代理类中使用hystrix做资源隔离,Feign代理类中 构造 RequestTemplate ,RequestTemlate要做的向负载均衡选中的server发送http请求,并进行编码和解码一系列操作。

    下面只是粗略的看了下整体流程,先有整体再有细节吧,下面利用IDEA看下细节:

    一、Feign失败重试

    SynchronousMethodHandler的方法中的处理逻辑:

    @Override
     public Object invoke(Object[] argv) throws Throwable {
      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;
       }
      }
     }
    上面的逻辑很简单。构造 template 并去进行服务间的http调用,然后对返回结果进行解码 当抛出 RetryableException 后,异常逻辑是否重试? 重试多少次? 带这个问题,看了retryer.continueOrPropagate(e);

    具体逻辑如下:

    public void continueOrPropagate(RetryableException e) {
       if (attempt++ >= maxAttempts) {
        throw e;
       }
     
       long interval;
       if (e.retryAfter() != null) {
        interval = e.retryAfter().getTime() - currentTimeMillis();
        if (interval > maxPeriod) {
         interval = maxPeriod;
        }
        if (interval < 0) {
         return;
        }
       } else {
        interval = nextMaxInterval();
       }
       try {
        Thread.sleep(interval);
       } catch (InterruptedException ignored) {
        Thread.currentThread().interrupt();
       }
       sleptForMillis += interval;
      }
    当重试次数大于默认次数5时候,直接抛出异常,不在重试 否则每隔一段时间 默认值最大1ms 后重试一次。

    这就Feign这块的重试这块的粗略逻辑,由于之前工作中一直使用dubbo。同样是否需要将生产环境中重试操作关闭?

    思考:之前dubbo生产环境的重试操作都会关闭。原因有几个:

    一般第一次失败,重试也会失败,极端情况下不断的重试,会占用大量dubbo连接池,造成连接池被打满,影响核心功能 也是比较重要的一点原因,重试带来的业务逻辑的影响,即如果接口不是幂等的,重试会带来业务逻辑的错误,引发问题

    二、Feign负载均衡策略

    那么负载均衡的策略又是什么呢?由上图中可知 executeAndDecode(template)

    Object executeAndDecode(RequestTemplate template) throws Throwable {
      Request request = targetRequest(template);
    
      if (logLevel != Logger.Level.NONE) {
       logger.logRequest(metadata.configKey(), logLevel, request);
      }
    
      Response response;
      long start = System.nanoTime();
      try {
       response = client.execute(request, options);
       // ensure the request is set. TODO: remove in Feign 10
       response.toBuilder().request(request).build();
      } catch (IOException e) {
       if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
       }
       throw errorExecuting(request, e);
      }
      long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
    
      boolean shouldClose = true;
      try {
       if (logLevel != Logger.Level.NONE) {
        response =
          logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
        // ensure the request is set. TODO: remove in Feign 10
        response.toBuilder().request(request).build();
       }
       if (Response.class == metadata.returnType()) {
        if (response.body() == null) {
         return response;
        }
        if (response.body().length() == null ||
            response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
         shouldClose = false;
         return response;
        }
        // Ensure the response body is disconnected
        byte[] bodyData = Util.toByteArray(response.body().asInputStream());
        return response.toBuilder().body(bodyData).build();
       }
       if (response.status() >= 200 && response.status() < 300) {
        if (void.class == metadata.returnType()) {
         return null;
        } else {
         return decode(response);
        }
       } else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
        return decode(response);
       } else {
        throw errorDecoder.decode(metadata.configKey(), response);
       }
      } catch (IOException e) {
       if (logLevel != Logger.Level.NONE) {
        logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
       }
       throw errorReading(request, response, e);
      } finally {
       if (shouldClose) {
        ensureClosed(response.body());
       }
      }
     }