使用feign client进行restful服务间的调用,除了要注意超时时间、retry的设置外,还有一个关于自定义异常的部分,须要注意一下,否则容易出错。html
Nginx 默认判断失败节点状态以connect refuse和time out状态为准,不以HTTP错误状态进行判断失败,由于HTTP只要能返回状态说明该节点还能够正常链接,因此nginx判断其仍是存活状态;除非添加了proxy_next_upstream指令设置对40四、50二、50三、50四、500和time out等错误进行转到备机处理。java
这个异常主要是用来适配IllegalArgumentException这类异常。HystrixBadRequestException与其余HystrixCommand抛出的异常不一样,该异常不会归入circuit breaker的统计里头,即不会触发熔断。nginx
/Users/xixicat/.m2/repository/io/github/openfeign/feign-core/9.3.1/feign-core-9.3.1-sources.jar!/feign/SynchronousMethodHandler.javagit
Object executeAndDecode(RequestTemplate template) throws Throwable { Request request = targetRequest(template); if (logLevel != Logger.Level.NONE) { logger.logRequest(metadata.configKey(), logLevel, request); } Response response; long start = System.nanoTime(); try { response = client.execute(request, options); // ensure the request is set. TODO: remove in Feign 10 response.toBuilder().request(request).build(); } catch (IOException e) { if (logLevel != Logger.Level.NONE) { logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start)); } throw errorExecuting(request, e); } long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start); boolean shouldClose = true; try { if (logLevel != Logger.Level.NONE) { response = logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime); // ensure the request is set. TODO: remove in Feign 10 response.toBuilder().request(request).build(); } if (Response.class == metadata.returnType()) { if (response.body() == null) { return response; } if (response.body().length() == null || response.body().length() > MAX_RESPONSE_BUFFER_SIZE) { shouldClose = false; return response; } // Ensure the response body is disconnected byte[] bodyData = Util.toByteArray(response.body().asInputStream()); return response.toBuilder().body(bodyData).build(); } 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); } } catch (IOException e) { if (logLevel != Logger.Level.NONE) { logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime); } throw errorReading(request, response, e); } finally { if (shouldClose) { ensureClosed(response.body()); } } }
其中对status code的处理见这段github
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); }
也就是feign client的处理跟nginx的是不同的,feign client把非200的以及404(
能够配置是否归入异常
)都算成error,都转给errorDecoder去处理了。spring
要特别注意,对于restful抛出的4xx的错误,也许大部分是业务异常,并非服务提供方的异常,所以在进行feign client调用的时候,须要进行errorDecoder去处理,适配为HystrixBadRequestException,好避开circuit breaker的统计,不然就容易误判,传几个错误的参数,立马就熔断整个服务了,后果不堪设想。segmentfault
附errorDecoder实例springboot
@Configuration public class BizExceptionFeignErrorDecoder implements feign.codec.ErrorDecoder{ private static final Logger logger = LoggerFactory.getLogger(BizExceptionFeignErrorDecoder.class); @Override public Exception decode(String methodKey, Response response) { if(response.status() >= 400 && response.status() <= 499){ return new HystrixBadRequestException("xxxxxx"); } return feign.FeignException.errorStatus(methodKey, response); } }