1. 程式人生 > >Feign Retryer的預設重試策略測試

Feign Retryer的預設重試策略測試

1、Feign配置 

@Configuration
public class FeignConfig {

    @Value("${coupon_service.url:http://localhost:8081}")
    private String couponServiceUrl;

    @Bean
    public CouponQueryServiceApi queryCouponServiceApi() {
        // connectTimeoutMillis=5*1000 連結超時時間
        // readTimeoutMillis=10*1000 響應超時時間,如果超過10秒沒有接過發起下一次請求
        Request.Options options = new Request.Options(5 * 1000, 10 * 1000);

        // period=100 發起當前請求的時間間隔,單位毫秒
        // maxPeriod=1000 發起當前請求的最大時間間隔,單位毫秒
        // maxAttempts=5 最多請求次數,包括第一次
        Retryer neverRetry = new Retryer.Default(100, 1000, 5);
        return Feign.builder()
                .encoder(new JacksonEncoder())
                .decoder(new JacksonDecoder())
                .options(options)
                .retryer(neverRetry)
                .target(CouponQueryServiceApi.class, couponServiceUrl);
     }

 }   

2、呼叫方用時 51273毫秒

3、服務方時間詳細記錄

次數 請求時間 響應時間 發起當前請求時間間隔
1 2019-04-26 11:18:03.042 2019-04-26 11:18:15.104 100*(1.5*0)=0
2 2019-04-26 11:18:13.202 2019-04-26 11:18:25.224 100*1.5*1=150
3 2019-04-26 11:18:23.436 2019-04-26 11:18:35.460 100*1.5*1.5=225
4 2019-04-26 11:18:33.781 2019-04-26 11:18:45.804 100*1.5*1.5*1.5=337
5 2019-04-26 11:18:44.292 2019-04-26 11:18:56.333 100*1.5*1.5*1.5*1.5=506
10秒中後超時 2019-04-26 11:18:54.292    
本次請求開始時間 2019-04-26 11:18:03.042
本次請求結束時間 2019-04-26 11:18:54.292
總耗時 51.25

4、核心方法

/**
 * feign.SynchronousMethodHandler#invoke
 */
@Override
public Object invoke(Object[] argv) throws Throwable {
  RequestTemplate template = buildTemplateFromArgs.create(argv);
  Retryer retryer = this.retryer.clone();
  while (true) {
    try {
      return executeAndDecode(template);
    } catch (RetryableException e) {
      retryer.continueOrPropagate(e);
      if (logLevel != Logger.Level.NONE) {
        logger.logRetry(metadata.configKey(), logLevel);
      }
      continue;
    }
  }
}
/**
 * feign.SynchronousMethodHandler#executeAndDecode
 */
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 && void.class != metadata.returnType()) {
        return decode(response);
      } 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());
      }
    }
  }
/**
 * feign.Retryer.Default#continueOrPropagate
 */
public void continueOrPropagate(RetryableException e) {
  if (attempt++ >= maxAttempts) {
    throw e;
  }

  long interval;
  if (e.retryAfter() != null) {
    interval = e.retryAfter().getTime() - currentTimeMillis();
    if (interval > maxPeriod) {
      interval = maxPeriod;
    }
    if (interval < 0) {
      return;
    }
  } else {
    interval = nextMaxInterval();
  }
  try {
    Thread.sleep(interval);
  } catch (InterruptedException ignored) {
    Thread.currentThread().interrupt();
  }
  sleptForMillis += interval;
}

/*
 * feign.Retryer.Default#nextMaxInterval
 */
long nextMaxInterval() {
   long interval = (long) (period * Math.pow(1.5, attempt - 1));
   return interval > maxPeriod ? maxPeriod : interval;
}