带有完整抖动 Java 实现的指数退避算法

unb*_*chy 3 java error-handling compiler-errors http retry-logic

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

调用方法如下所示:

HttpResponse response = executeGetRequest( params );
int statusCode = response.getStatusCode();
//some status code validation
Run Code Online (Sandbox Code Playgroud)

我的 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 );
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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 );
Run Code Online (Sandbox Code Playgroud)

任何帮助将不胜感激!

编辑: 响应实际上位于尝试资源中。

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

我遇到两个问题:

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

tgd*_*ies 5

您可以在课堂上引入更多样板,例如:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以这样使用:

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

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

我已将可变状态(attempts,并且backoffValue可以删除)移出类并移至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 );
    }
Run Code Online (Sandbox Code Playgroud)

这是未经测试的代码!

您可能也应该在重试几次后停止重试。

为了测试这个你。希望将睡眠和随机数生成放入单独的组件中,然后注入到ExponentialBackoffStrategy. 您的静态工厂方法可以注入生产实现,并且您的测试将使用ExponentialBackoffStrategy构造函数并传递模拟。

所以你会有接口:

interface RandomNumber {
   int next();
}

interface Sleeper {
   void sleep(long milliseconds);
}
Run Code Online (Sandbox Code Playgroud)

和一个构造函数:

protected ExponentialBackoffStrategy(long maxBackoff, RandomNumber randomNumber, Sleeper sleeper) {
...
}
Run Code Online (Sandbox Code Playgroud)