问题描述
我正在尝试用纯 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;
}
);)
我遇到了两个问题:
- 上线了
final int statusCode = response.getStatusLine().getStatusCode();
“响应”带有红色下划线“变量'响应'可能尚未初始化”。试图将它带出 try 块并尝试使用资源不喜欢它。 - 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;
}
);
这样可以减少程序中重复代码的数量,并且可以测试一次重试逻辑。
我已将可变状态(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 );
}
这是未经测试的代码!
您也应该在重试一定次数后停止重试。
为了测试你。想要将睡眠和随机数生成都放入注入 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);
}