feign异常feign.RetryableException & NoHttpResponseException

系统总是出现feign.RetryableException,需要找到原因并解决,虽然问题不大,但是一直有这个异常日志。
项目采用的是spring-cloud-starter-openfeign自动注入、apache httpclient做连接池。并没有对配置做相关优化,采用默认配置。

feign.RetryableException定位

通过异常栈定位到问题出在feign.SynchronousMethodHandler#invoke内调用executeAndDecode。执行请求出现异常后抛出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
//执行请求,里面有抛出RetryableException
return executeAndDecode(template, options);
} 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;
}
}
}

默认情况下feign配置重试机制使用的是feign.Retryer.Default,默认重试5次。
在使用了SpringBoot自动配置后,在org.springframework.cloud.openfeign.FeignClientsConfiguration#feignRetryer可以看到默认使用的是feign.Retryer#NEVER_RETRY,里面是不做重试直接抛出异常。那就可以看到为什么请求失败会出现feign.RetryableException

NoHttpResponseException 定位

在上面feign异常栈的后面可以看到NoHttpResponseException异常,通过异常栈可以看到代码org.apache.http.impl.conn.DefaultHttpResponseParser#parseHead处抛出的异常,是读取返回时出错抛出。

从stackoverflow Apache HttpClient Interim Error: NoHttpResponseException里面可以看到,根本原因是因为使用了过期连接导致。

解决办法:

  • 官方建议定时清理过期连接。
  • 设置自定义重试次数。
  • 使用okhttp替换apache httpclient

使用okhttp添加配置feign.httpclient.enabled=false,避免第三方依赖了feign-httpclient,如果确认没有依赖可以不需要
添加feign.okhttp.enabled=true启动okhttp