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

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

概述

最近在使用 Feign (io.github.openfeign) 进行服务间调用

测了几种极端场景

断网 -> 返回空对象(不是null)

超时 -> 抛出 feign.RetryableException 异常,具体信息是 java.net.SocketTimeoutException 异常

远端抛异常 -> 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;

}

}

}

逻辑如下

无限循环

在第一次请求executeAndDecode抛出异常之后,由重试器决定到底是继续请求还是抛出异常终止请求 continueOrPropagate

如果重试器重试次数达到阈值,也会抛出异常

解决

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());

}

改动点有两处

取消了无限循环

使用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))

;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值