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))
;
网友评论