Feign: Retry depending on response status

2020-06-04 09:01发布

问题:

I am currently using Spring Cloud and Feign to consume a Microservice in my application. Since it can happen, that a database connection or the like fails in a single service instance, making it return 500 HTTP status code, I want to make sure, that the next server is retried by the service's clients. Currently, Ribbon's retry mechanism works like a charm when the service is not running at all, however it still returns instantly an error when it receives a 500 status code, without any retry.

Is it possible to configure the Feign clients or their underlying Ribbon load balancers to retry the next server, if an instance returns a 500 response?

The configuration is pretty much the same as in this thread: Does Feign retry require some sort of configuration?

I would love to use an implementation like Ribbons' HttpResponseValidator (https://github.com/Netflix/ribbon/blob/master/ribbon/src/main/java/com/netflix/ribbon/http/HttpResponseValidator.java), but I couldn't find anything usable for Spring Cloud and its Feign/Ribbon integration

回答1:

This question is very old and the solution was probably already found or wasn't possible at the time. Anyway, I think that answer might still help someone 8 ). Please use this as a reference, this code is not intended for production use. Feign allows you to configure errorDecoder - this is the place where magic happens.

Feign.Builder builder = Feign.builder()
  .errorDecoder(new RetryOnScaleErrorDecoder())

Here is the implementation, I use that class to retry request on HTTP error 429 I get from AWS when service is scaling

public static class RetryOnScaleErrorDecoder implements ErrorDecoder {

  @Override
  public Exception decode(String methodKey, Response response) {
    FeignException exception = errorStatus(methodKey, response);
    // This is a terrible part please check how feign.codec.ErrorDecoder.RetryAfterDecoder is implemented for proper parsing of retry-after header
    Collection<String> headers = response.headers().get("Retry-After");

    String repeatAfterString = "0";
    if (Objects.nonNull(headers)) {
      repeatAfterString = Iterables.getFirst(headers, "0");
    }

    assert repeatAfterString != null;

    Date repeatAfter = new Date(currentTimeMillis());

    if (repeatAfterString.matches("^[0-9]+$")) {
      try {
        long deltaMillis = SECONDS.toMillis(Long.parseLong(repeatAfterString));
        repeatAfter = new Date(currentTimeMillis() + deltaMillis);
      } catch (NumberFormatException ignored) {
        // TODO: logging
      }
    }
    // That's the part where we decide to retry based on status code value
    if (exception.status() == 429) {
      return new RetryableException(
          response.status(),
          exception.getMessage(),
          response.request().httpMethod(),
          exception,
          repeatAfter
      );
    }
    return exception;
  }
}

I think that in conjunction with Ribbon it will produce desired result.