關於feign client觸發熔斷的異常

使用feign client進行restful服務間的調用,除了要注意超時時間、retry的設置外,還有一個關於自定義異常的部分,須要注意一下,否則容易出錯。html

nginx對於upstream的health check

Nginx 默認判斷失敗節點狀態以connect refuse和time out狀態爲準,不以HTTP錯誤狀態進行判斷失敗,由於HTTP只要能返回狀態說明該節點還能夠正常鏈接,因此nginx判斷其仍是存活狀態;除非添加了proxy_next_upstream指令設置對40四、50二、50三、50四、500和time out等錯誤進行轉到備機處理。java

feign及hystrix對於服務提供方的health check

HystrixBadRequestException

這個異常主要是用來適配IllegalArgumentException這類異常。HystrixBadRequestException與其餘HystrixCommand拋出的異常不一樣,該異常不會歸入circuit breaker的統計裏頭,即不會觸發熔斷。nginx

feign client對restful調用的異常處理

/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);
    }
}

doc

相關文章
相關標籤/搜索