On the abnormality of feigninclient triggering fusing

  springboot

Order

When using feign client to make calls between restful services, in addition to paying attention to the setting of timeout and retry, there is also a part about custom exceptions, which needs attention, otherwise it is easy to make mistakes.

Nginx’s health check for upstream

Nginx judges the failed node status by default based on the connect refuse and time out status, and does not judge failure based on HTTP error status. As long as HTTP can return status indicating that the node can still connect normally, Nginx judges whether it is still alive. Unless proxy_next_upstream instruction setting is added, errors such as 404, 502, 503, 504, 500 and time out are transferred to the standby machine for processing.

Feign and hystrix’s health check for service providers

HystrixBadRequestException

This exception is mainly used to adapt to exceptions such as IllegalArgumentException. HystrixBadRequestException is different from the exception thrown by other HystrixCommand. The exception will not be included in circuit breaker’s statistics, that is, it will not trigger fusing.

Exception handling of restful calls by feigninclient

/Users/xixicat/.m2/repository/io/github/openfeign/feign-core/9.3.1/feign-core-9.3.1-sources.jar! /feign/SynchronousMethodHandler.java

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

See this paragraph for the treatment of status code.

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

In other words, feign client’s processing is different from nginx’s. feign client sends non-200 and 404 (You can configure whether exceptions are included) are calculated as error, are transferred to the errorDecoder to deal with.

Summary

Special attention should be paid to the 4xx errors thrown by restful. Perhaps most of them are business exceptions, not service provider exceptions. Therefore, when feign client is called, errorDecoder is required to handle them, and it is adapted as HystrixBadRequestException, so as to avoid the statistics of circuit breaker. Otherwise, it is easy to misjudge, pass a few wrong parameters, and immediately blow the whole service, with unimaginable consequences.

  • Attached is an example of errorDecoder.

@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