本文介绍一种本地退避重试的实施方案,可以解决偶然网络抖动下的系统重试问题。但是使用方须特别注意,本地退避重试的缺陷即为在单机hang死的场景,本地重试会加重这种问题,使用场景较为局限设计方案上须着重考虑这个缺点。
1.定义本地重试策略:
使用org.springframework.util.backoff.ExponentialBackOff 作为重试定义:
/**
* 退避策略
*/
private ExponentialBackOff backOff;
/**
* 初始化退避重试逻辑
*/
@PostConstruct
public void init() {
//初始间隔
long initialInterval = 50L;
//最大间隔
//When the interval has reached the max interval, it is no longer increased.
long maxInterval = 150L;
//最大时间间隔(所有停顿累加)本设置第一次退避50,第二次退避100,第三次退避150ms,第四次退避时返回stop不再执行退避
//Stops retrying once the max elapsed time has been reached.
long maxElapsedTime = 160L;
//递增倍数(即下次间隔是上次的多少倍)
double multiplier = 2;
backOff = new ExponentialBackOff(initialInterval, multiplier);
backOff.setMaxInterval(maxInterval);
//currentElapsedTime = interval1 + interval2 + ... + intervalN;
backOff.setMaxElapsedTime(maxElapsedTime);
}
2.实现示例(本文以http-component 为例,介绍异步请求下的退避重试实现):
import javax.annotation.PostConstruct;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.springframework.util.backoff.BackOffExecution;
import org.springframework.util.backoff.ExponentialBackOff;
public class HttpClient {
/**
* 退避策略
*/
private ExponentialBackOff backOff;
/**
* Http异步客户端实例。( 配置实例化)
*/
private CloseableHttpAsyncClient asyncHttpClient;
/**
* 初始化退避重试逻辑
*/
@PostConstruct
public void init() {
//初始间隔
long initialInterval = 50L;
//最大间隔
//When the interval has reached the max interval, it is no longer increased.
long maxInterval = 150L;
//最大时间间隔(所有停顿累加)本设置第一次退避50,第二次退避100,第三次退避150ms,第四次退避时返回stop不再执行退避
//Stops retrying once the max elapsed time has been reached.
long maxElapsedTime = 160L;
//递增倍数(即下次间隔是上次的多少倍)
double multiplier = 2;
backOff = new ExponentialBackOff(initialInterval, multiplier);
backOff.setMaxInterval(maxInterval);
//currentElapsedTime = interval1 + interval2 + ... + intervalN;
backOff.setMaxElapsedTime(maxElapsedTime);
}
/**
* 异步执行Http请求。
*
* @param httpUriRequest 请求对象。
*/
public void postWithRetry(HttpUriRequest httpUriRequest){
postWithRetry(httpUriRequest, backOff.start());
}
/**
* 异步执行Http请求。
*
* @param httpUriRequest 请求对象。
*/
private void postWithRetry(HttpUriRequest httpUriRequest, BackOffExecution backOffExecution){
long currentBackOff = backOffExecution.nextBackOff();
if (currentBackOff == BackOffExecution.STOP) {
// do something
return;
}
asyncHttpClient.execute(httpUriRequest, new AsyncHttpCallback(backOffExecution, currentBackOff, httpUriRequest));
}
/**
* http 执行结果处理
*/
class AsyncHttpCallback implements FutureCallback<HttpResponse> {
private BackOffExecution backOffExecution;
private long currentBackOff;
private HttpUriRequest httpUriRequest;
AsyncHttpCallback(BackOffExecution backOffExecution,
long currentBackOff, HttpUriRequest httpUriRequest) {
this.backOffExecution = backOffExecution;
this.currentBackOff = currentBackOff;
this.httpUriRequest = httpUriRequest;
}
@Override
public void completed(HttpResponse httpResponse) {
int statusCode = httpResponse.getStatusLine().getStatusCode();
boolean ok = statusCode >= HttpStatus.SC_OK && statusCode < HttpStatus.SC_MULTIPLE_CHOICES;
if (ok) {
// do something
} else {
// do something
}
}
@Override
public void failed(Exception e) {
// do something
try {
// 退避
long start = System.currentTimeMillis();
synchronized (backOffExecution) {
backOffExecution.wait(currentBackOff);
}
// do something
} catch (InterruptedException ex) {
// Re-interrupt current thread, to allow other threads to react.
Thread.currentThread().interrupt();
// do something
}
// 重试
postWithRetry(httpUriRequest, backOffExecution);
}
@Override
public void cancelled() {
// do something
try {
// 退避
long start = System.currentTimeMillis();
synchronized (backOffExecution) {
backOffExecution.wait(currentBackOff);
}
// do something
} catch (InterruptedException ex) {
// Re-interrupt current thread, to allow other threads to react.
Thread.currentThread().interrupt();
// do something
}
// 重试
postWithRetry(httpUriRequest, backOffExecution);
}
}
}
测试UT:
@Test
public void testInit() {
// Setup
// Run the test
httpClient.init();
ExponentialBackOff exponentialBackOff = kmHttpClientUnderTest.getBackOff();
// Verify the results
BackOffExecution backOffExecution = exponentialBackOff.start();
long back0 = backOffExecution.nextBackOff();
Assert.assertEquals(500, back0);
long back1 = backOffExecution.nextBackOff();
Assert.assertEquals(1000, back1);
Assert.assertEquals(BackOffExecution.STOP, backOffExecution.nextBackOff());
}
以上实例中给出了本地重试的实现示例,提供了异步请求场景下退避重试的设计思路,欢迎评论讨论。关注收藏。