美文网首页
Feign 在远端接口超时的时候,抑制异常的抛出

Feign 在远端接口超时的时候,抑制异常的抛出

作者: Yellowtail | 来源:发表于2019-05-13 16:44 被阅读0次

restrain feign.RetryableException or java.net.SocketTimeoutException when read timeout

概述

最近在使用 Feign (io.github.openfeign) 进行服务间调用
测了几种极端场景

  1. 断网 -> 返回空对象(不是null)
  2. 超时 -> 抛出 feign.RetryableException 异常,具体信息是 java.net.SocketTimeoutException 异常
  3. 远端抛异常 -> feign 也抛异常

第一个不用处理,第三个让远端处理一下(我们自己的服务)
现在要解决的是第二个场景,我想做成不重试、不抛异常 的效果(这是目前的想法,至于这么做到底好不好,那就是另外一个问题了)

源码

我用的是 10.2.0 的包
代码链接
方法是 invoke

@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) {
        try {
          retryer.continueOrPropagate(e);
        } catch (RetryableException th) {
          Throwable cause = th.getCause();
          if (propagationPolicy == UNWRAP && cause != null) {
            throw cause;
          } else {
            throw th;
          }
        }
        if (logLevel != Logger.Level.NONE) {
          logger.logRetry(metadata.configKey(), logLevel);
        }
        continue;
      }
    }
  }

逻辑如下

  1. 无限循环
  2. 在第一次请求executeAndDecode抛出异常之后,由重试器决定到底是继续请求还是抛出异常终止请求 continueOrPropagate
  3. 如果重试器重试次数达到阈值,也会抛出异常

解决

1. 重试机制

首先改成 不重试

Retryer 接口包含一个实例 NEVER_RETRY

Retryer NEVER_RETRY = new Retryer() {

    @Override
    public void continueOrPropagate(RetryableException e) {
      throw e;
    }

    @Override
    public Retryer clone() {
      return this;
    }
  };

2. 不抛异常

从上面的代码可以看出来,NEVER_RETRY 虽然不重试,但是会抛异常
所以我们自己实现一个

public static class OurRetryer implements Retryer {
        @Override
        public void continueOrPropagate(RetryableException e) {
            LOGGER.warn("request occur RetryableException, ", e);
        }

        @Override
        public Retryer clone() {
          return this;
        }
    }

3. 退出循环

我们自己的 OurRetryer 不抛异常了,但是问题来了,无法退出无限循环
刚开始想着 override 一下 SynchronousMethodHandler 的方法,但是这是个 final

没办法了,只能修改源码了
我改成了下面这个样子

@Override
  public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Retryer retryer = this.retryer.clone();

    try {
      return executeAndDecode(template);
    } catch (RetryableException e) {
      retryer.continueOrPropagate(e);
      if (logLevel != Logger.Level.NONE) {
        logger.logRetry(metadata.configKey(), logLevel);
      }
    }

    return decoder.decode(null, metadata.returnType());
  }

链接

改动点有两处

  1. 取消了无限循环
  2. 使用decoder 返回了一个对象(这个对象就是一个vo

return 语句里的 null 参数 后面会讲到

4. decoder

Object decode(Response response) throws Throwable {
    try {
      return decoder.decode(response, metadata.returnType());
    } catch (FeignException e) {
      throw e;
    } catch (RuntimeException e) {
      throw new DecodeException(response.status(), e.getMessage(), e);
    }
  }

上面是 executeAndDecode 里的 返回对象的实现
可以看到就是调用了 我们在 build Feign 的时候,传入的 decoder 的 decode 方法

我之前用的是 JacksonDecoder

@Override
  public Object decode(Response response, Type type) throws IOException {
    if (response.status() == 404)
      return Util.emptyValueOf(type);
    if (response.body() == null)
      return null;
    Reader reader = response.body().asReader();
    if (!reader.markSupported()) {
      reader = new BufferedReader(reader, 1);
    }

可以看到 Response 对象一定不为null的
所以我们自己 写一个 decoder , 来处理 null 场景(这也就是退出循环 那里的 null 参数的由来)

改动很简单,完全照抄 JacksonDecoder, 然后改动一下 decode

    @Override
    public Object decode(Response response, Type type) throws IOException {
        //修改点
        if (null == response) {
            return mapper.readValue("{}", mapper.constructType(type));
        }
        
        //以下代码无变化

jackson 会把 {} 这样的字符串 反序列化为 一个对象的对象,而不是 null
刚好满足我们的要求

build feign

改了这么多之后,最后build 实例的时候变成了这样

Feign.builder()
    .encoder(new JacksonEncoder())
    .client(new ApacheHttpClient())
    .options(new Request.Options(CONNECT_TIMEOUT_MILLIS, READ_TIMEOUT_MILLIS))
    .retryer(new OurRetryer())      // 设置不重试
    .decoder(new OurJacksonDecoder(JacksonDecoderUtil.OBJECT_MAPPER))
    ;

相关文章

网友评论

      本文标题:Feign 在远端接口超时的时候,抑制异常的抛出

      本文链接:https://www.haomeiwen.com/subject/focraqtx.html