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

Exponential Backoff Algorithm with Full Jitter Java Implementation

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

调用方法如下所示:

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

我的 ExponentialBackoffStrategy class 是:

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

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

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(); “响应”带有红色下划线“变量 'response' 可能尚未初始化”。试图将它带到 try 块之外并尝试使用不喜欢的资源。
  2. executeGetRequest 现在需要 lambda 内部的 catch 块: try ( HttpResponse response = executePostRequest( params ) ) {

您可以将更多样板文件放入 class,例如:

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 可以删除)从 class 移到 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) {
...
}

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

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

if tell mx_interal as 2000L - 那么第一次尝试应该是 2000L,如果计算应该落在 1 > < 2000L 之间,则第二次尝试。使它始终在这个范围内。

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