具有完全抖动 Java 实现的指数退避算法

问题描述

我正在尝试用纯 Java 创建一个实用程序类,该类将包含指数退避算法实现所需的逻辑,并具有完全抖动,因为将有多个客户端发送请求。 我有一个类,其方法执行 GET 或 POST 请求并返回带有状态代码的响应。仅当状态代码在 5xx 中时,我才想重试(也就是使用指数退避策略)。当前代码未编译。

调用方法如下:

HttpResponse response = executeGetRequest( params );
int statusCode = response.getStatusCode();
//some status code validation

我的 ExponentialBackoffStrategy 类是:

public class ExponentialBackoffStrategy {

private final long maxBackoff;

private long backoffValue;

private long attempts;
private static final long DEFAULT_MAX_RETRIES = 900_000;

private Random random = new Random();

public ExponentialBackoffStrategy( long maxBackoff ) {
    this.maxBackoff = maxBackoff;
}

public long getWaitTimeExp() {
    if( backoffValue >= maxBackoff ) {
        return maxBackoff;
    }
    double pow = Math.pow( 2,attempts++ );
    int rand = random.nextInt( 1000 );
    backoffValue = ( long ) Math.min( pow + rand,maxBackoff );
    return backoffValue;
}

public static ExponentialBackoffStrategy getDefault() {
    return new ExponentialBackoffStrategy( DEFAULT_MAX_RETRIES );
    }
}

我想得到一些关于实现类的反馈,关于我是否可以做得更好,以及如何将它与调用方法集成。我现在的想法是:

ExponentialBackoffStrategy backoff = ExponentialBackoffStrategy.getDefault();
boolean retry = false;
HttpResponse response = null;
int statusCode = 0;
do {
  response = executeGetRequest( params );
  statusCode = response.getStatusLine().getStatusCode();
  if( statusCode >= 500 && statusCode < 600 ) {
    retry = true;
    try {
        Thread.sleep( backoff.getWaitTimeExp() );
    } catch ( InterruptedException e ) {
        //handle exception
    }
  }
} while ( retry );

任何帮助将不胜感激!

编辑: 响应实际上位于 try with resources 中。

try ( HttpResponse response = backoff.attempt(
() -> executeGetRequest( params ),r -> {
  final int statusCode = response.getStatusLine().getStatusCode();
  return statusCode < 500 || statusCode >= 600;
}
);)

我遇到了两个问题:

  1. 上线了 final int statusCode = response.getStatusLine().getStatusCode(); “响应”带有红色下划线“变量'响应'可能尚未初始化”。试图将它带出 try 块并尝试使用资源不喜欢它。
  2. executeGetRequest 现在需要在 lambda 内部有一个 catch 块: try ( HttpResponse response = executePostRequest( params ) ) {

解决方法

您可以将更多样板文件带入课堂,例如:

public class ExponentialBackoffStrategy {
...
    @Nullable
    public <T> T attempt(Supplier<T> action,Predicate<T> success) {
        int attempts = 0;

        T result = action.get();
        while (!success.test(result)) {
            try {
                Thread.sleep(getWaitTimeExp(attempts++));
            } catch ( InterruptedException e ) {
                //handle exception
            }
            result = action.get();
        }
        return result;
    }
}

然后你会像这样使用:

        ExponentialBackoffStrategy backoff = ExponentialBackoffStrategy.getDefault();
        final HttpResponse response = backoff.attempt(
                () -> executeGetRequest( params ),r -> {
                    final int statusCode = r.getStatusLine().getStatusCode();
                    return statusCode < 500 || statusCode >= 600;
                }
        );

这样可以减少程序中重复代码的数量,并且可以测试一次重试逻辑。

我已将可变状态(attemptsbackoffValue 可以删除)移出类并移到 attempt() 函数中的局部变量中。这意味着单个 ExponentialBackoffStrategy 实例可以安全地重用,也可以被多个线程使用。所以 getWaitTimeExp 变成了一个没有副作用的函数:

    private long getWaitTimeExp(int attempts) {
        final double pow = Math.pow( 2,attempts);
        final int rand = random.nextInt( 1000 );
        return ( long ) Math.min( pow + rand,maxBackoff );
    }

这是未经测试的代码!

您也应该在重试一定次数后停止重试。

为了测试你。想要将睡眠和随机数生成都放入注入 ExponentialBackoffStrategy 的单独组件中。您的静态工厂方法可以注入生产实现,而您的测试将使用 ExponentialBackoffStrategy 构造函数并传递模拟。

这样你就有了接口:

interface RandomNumber {
   int next();
}

interface Sleeper {
   void sleep(long milliseconds);
}

和一个构造函数:

protected ExponentialBackoffStrategy(long maxBackoff,RandomNumber randomNumber,Sleeper sleeper) {
...
}
,

以上是指数退避,但没有随机睡眠时间。我试过给随机时间。

如果这个逻辑看起来不错并且我想要最大间隔时间范围。

如果告诉 mx_interal 为 2000L - 那么第一次尝试应该是 2000L,如果计算应该介于 1 >

private long getWaitTimeExp(int retries) {         
    return getWaitTimeExp(retries,withDelayBetweenTries(maxWaitInterval,ChronoUnit.MILLIS)).toMillis();
}
    public Duration getWaitTimeExp(int numberOfTriesFailed,Duration delayBetweenAttempts) {
    int i = ThreadLocalRandom.current().nextInt(1,this.maxMultiplier);
    return getDurationToWait(numberOfTriesFailed,Duration.ofMillis(i * delayBetweenAttempts.toMillis()));
}

public Duration getDurationToWait(int numberOfTriesFailed,Duration delayBetweenAttempts) {
    double exponentialMultiplier = Math.pow(2.0,numberOfTriesFailed);
    double result = exponentialMultiplier * delayBetweenAttempts.toMillis();
    long millisToWait = (long) Math.min(result,Long.MAX_VALUE);
    return Duration.ofMillis(millisToWait);
}

public static Duration withDelayBetweenTries(long amount,ChronoUnit time) {
    return Duration.of(amount,time);
}