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