今在线上遇到了一个比较奇怪的问题, 现象是:
两个服务P
, C
分别注册到Eureka
中, P
为服务提供方, C
为调用方, 上线升级完成后, C
调用P
某个接口失败, 并且没有异常日志, 也没有经过ErrorDecoder
, 而是进入Fallback方法中, 调查过程如下:
首先, 我直接调用了P
提供接口, 发现返回结果正常, 说明不是P
服务的问题.
接下来, 继续查看同事的代码, 看看Fallback
以及日志切面中是否存在有价值的信息, 可惜Fallback
只是简单的记录了调用失败.
然后, 我通过单独启动一个P
服务, 将register-with-eureka
配置为false
, 使其不会被gateway
以及其他服务发现, 从而达到不影响系统整体调用的效果, 有了这样一个服务, 本地就可以进行debug
了, 经过简单的调试发现, 问题出在接口返回值的转型上, 由于POM
文件中依赖版本问题, 生产环境jar
中少了一个DTO的类, 而出问题的接口的DTO中恰好持有了一个该类的集合引用: List<SomeDTO>
, 导致DecodeException
.
将问题修复后, 我又继续查看了为什么没有经过ErrorDecoder
的原因:
在feign
调用时, 会经过一个核心方法SynchronousMethodHandler.executeAndDecode
, 在此方法中, 会执行feignClient
的请求, 并进行结果的转换, 在本文的bug情况里, feignClient
的请求的返回是正常的, status
为200
, 因此并不会触发errorDecoder.decode
, 而我们的异常则会在decode(response)
是被抛出:
Object executeAndDecode(RequestTemplate template) throws Throwable {
/*
...
*/
try {
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 10
response.toBuilder().request(request).build();
} catch (IOException e) {
/*
...
*/
}
/*
...
*/
try {
/*
...
*/
// 本文的情况, 会进入第一个分支, 而不是第三个(errorDecoder的处理)
if (response.status() >= 200 && response.status() < 300) {
if (void.class == metadata.returnType()) {
return null;
} else {
return decode(response);
}
} else if (decode404 && response.status() == 404) {
return decoder.decode(response, metadata.returnType());
} else {
throw errorDecoder.decode(metadata.configKey(), response);
}
}
/*
...
*/
}
这种情况, 单纯的在fallback
方法里是没有办法看到异常原因的, 因为fallback
方法内并没有被动态注入任何异常参数, 经过对spring-cloud-netflix
的查看, 发现这个问题之前已经有人提出过, 并且已经解决, 原理是支持了fallbackFactory
的自定义配置, 因为在HystrixClient
创建时, 我们是可以拿到异常信息的:
@FeignClient(name = "hello", fallbackFactory = HystrixClientFallbackFactory.class)
protected interface HystrixClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello iFailSometimes();
}
@Component
static class HystrixClientFallbackFactory implements FallbackFactory<HystrixClient> {
@Override
public HystrixClient create(Throwable cause) {
return new HystrixClientWithFallBackFactory() {
@Override
public Hello iFailSometimes() {
return new Hello("fallback; reason was: " + cause.getMessage());
}
};
}
}