Skip to content

Commit 7b81255

Browse files
authored
feat: improve retry strategy (#196)
close #195
1 parent e405ca6 commit 7b81255

File tree

2 files changed

+90
-23
lines changed

2 files changed

+90
-23
lines changed

src/main/java/io/github/doocs/im/ClientConfiguration.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ public class ClientConfiguration {
2222
*/
2323
public static final int DEFAULT_MAX_RETRIES = 3;
2424

25+
/**
26+
* 默认重试间隔时间(毫秒)
27+
*/
28+
public static final long DEFAULT_RETRY_INTERVAL_MS = 200;
29+
2530
/**
2631
* 默认自动更新签名
2732
*/
@@ -45,6 +50,7 @@ public class ClientConfiguration {
4550
public static final ConnectionPool DEFAULT_CONNECTION_POOL = new ConnectionPool();
4651

4752
private int maxRetries = DEFAULT_MAX_RETRIES;
53+
private long retryIntervalMs = DEFAULT_RETRY_INTERVAL_MS;
4854
private long connectTimeout = DEFAULT_CONNECT_TIMEOUT;
4955
private long readTimeout = DEFAULT_READ_TIMEOUT;
5056
private long writeTimeout = DEFAULT_WRITE_TIMEOUT;
@@ -57,13 +63,14 @@ public class ClientConfiguration {
5763
public ClientConfiguration() {
5864
}
5965

60-
public ClientConfiguration(int maxRetries, long connectTimeout, long readTimeout, long writeTimeout,
66+
public ClientConfiguration(int maxRetries, long retryIntervalMs, long connectTimeout, long readTimeout, long writeTimeout,
6167
long callTimeout, long expireTime, boolean autoRenewSig,
6268
String userAgent, ConnectionPool connectionPool) {
6369
if (connectionPool == null) {
6470
connectionPool = DEFAULT_CONNECTION_POOL;
6571
}
6672
this.maxRetries = Math.max(0, maxRetries);
73+
this.retryIntervalMs = retryIntervalMs;
6774
this.connectTimeout = connectTimeout;
6875
this.readTimeout = readTimeout;
6976
this.writeTimeout = writeTimeout;
@@ -76,6 +83,7 @@ public ClientConfiguration(int maxRetries, long connectTimeout, long readTimeout
7683

7784
private ClientConfiguration(Builder builder) {
7885
this.maxRetries = builder.maxRetries;
86+
this.retryIntervalMs = builder.retryIntervalMs;
7987
this.connectTimeout = builder.connectTimeout;
8088
this.readTimeout = builder.readTimeout;
8189
this.writeTimeout = builder.writeTimeout;
@@ -98,6 +106,14 @@ public void setMaxRetries(int maxRetries) {
98106
this.maxRetries = Math.max(0, maxRetries);
99107
}
100108

109+
public long getRetryIntervalMs() {
110+
return retryIntervalMs;
111+
}
112+
113+
public void setRetryIntervalMs(long retryIntervalMs) {
114+
this.retryIntervalMs = retryIntervalMs;
115+
}
116+
101117
public long getConnectTimeout() {
102118
return connectTimeout;
103119
}
@@ -178,6 +194,9 @@ public boolean equals(Object o) {
178194
if (maxRetries != that.maxRetries) {
179195
return false;
180196
}
197+
if (retryIntervalMs != that.retryIntervalMs) {
198+
return false;
199+
}
181200
if (connectTimeout != that.connectTimeout) {
182201
return false;
183202
}
@@ -204,11 +223,12 @@ public boolean equals(Object o) {
204223

205224
@Override
206225
public int hashCode() {
207-
return Objects.hash(maxRetries, connectTimeout, readTimeout, writeTimeout, callTimeout, expireTime, autoRenewSig, userAgent, connectionPool);
226+
return Objects.hash(maxRetries, retryIntervalMs, connectTimeout, readTimeout, writeTimeout, callTimeout, expireTime, autoRenewSig, userAgent, connectionPool);
208227
}
209228

210229
public static final class Builder {
211230
private int maxRetries = DEFAULT_MAX_RETRIES;
231+
private long retryIntervalMs = DEFAULT_RETRY_INTERVAL_MS;
212232
private long connectTimeout = DEFAULT_CONNECT_TIMEOUT;
213233
private long readTimeout = DEFAULT_READ_TIMEOUT;
214234
private long writeTimeout = DEFAULT_WRITE_TIMEOUT;
@@ -230,6 +250,11 @@ public Builder maxRetries(int maxRetries) {
230250
return this;
231251
}
232252

253+
public Builder retryIntervalMs(int retryIntervalMs) {
254+
this.retryIntervalMs = retryIntervalMs;
255+
return this;
256+
}
257+
233258
public Builder connectTimeout(long connectTimeout) {
234259
this.connectTimeout = connectTimeout;
235260
return this;

src/main/java/io/github/doocs/im/util/HttpUtil.java

Lines changed: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
import okhttp3.*;
55

66
import java.io.IOException;
7-
import java.util.HashMap;
8-
import java.util.Map;
9-
import java.util.Objects;
7+
import java.util.*;
108
import java.util.concurrent.ConcurrentHashMap;
119
import java.util.concurrent.TimeUnit;
10+
import java.util.stream.Collectors;
11+
import java.util.stream.Stream;
1212

1313
/**
1414
* HTTP 工具类
@@ -31,7 +31,7 @@ public class HttpUtil {
3131
.writeTimeout(DEFAULT_CONFIG.getWriteTimeout(), TimeUnit.MILLISECONDS)
3232
.callTimeout(DEFAULT_CONFIG.getCallTimeout(), TimeUnit.MILLISECONDS)
3333
.retryOnConnectionFailure(false)
34-
.addInterceptor(new RetryInterceptor(DEFAULT_CONFIG.getMaxRetries()))
34+
.addInterceptor(new RetryInterceptor(DEFAULT_CONFIG.getMaxRetries(), DEFAULT_CONFIG.getRetryIntervalMs()))
3535
.build();
3636

3737
private HttpUtil() {
@@ -58,7 +58,7 @@ private static OkHttpClient getClient(ClientConfiguration config) {
5858
.writeTimeout(cfg.getWriteTimeout(), TimeUnit.MILLISECONDS)
5959
.callTimeout(cfg.getCallTimeout(), TimeUnit.MILLISECONDS)
6060
.retryOnConnectionFailure(false)
61-
.addInterceptor(new RetryInterceptor(cfg.getMaxRetries()))
61+
.addInterceptor(new RetryInterceptor(cfg.getMaxRetries(), cfg.getRetryIntervalMs()))
6262
.build());
6363
}
6464

@@ -98,28 +98,70 @@ public static String get(String url) throws IOException {
9898
}
9999

100100
class RetryInterceptor implements Interceptor {
101-
private int maxRetry;
102-
103-
RetryInterceptor(int maxRetry) {
104-
this.maxRetry = maxRetry;
101+
private static final Set<Integer> RETRYABLE_STATUS_CODES = Collections.unmodifiableSet(
102+
Stream.of(408, 429, 500, 502, 503, 504).collect(Collectors.toSet())
103+
);
104+
private static final int MAX_DELAY_MS = 10000;
105+
private final int maxRetries;
106+
private final long retryIntervalMs;
107+
108+
public RetryInterceptor(int maxRetries, long retryIntervalMs) {
109+
this.maxRetries = maxRetries;
110+
this.retryIntervalMs = retryIntervalMs;
105111
}
106112

107-
public int getMaxRetry() {
108-
return maxRetry;
113+
@Override
114+
public Response intercept(Chain chain) throws IOException {
115+
Request request = chain.request();
116+
Response response = null;
117+
IOException exception = null;
118+
for (int attempt = 0; attempt <= maxRetries; ++attempt) {
119+
if (response != null) {
120+
response.close();
121+
}
122+
try {
123+
response = chain.proceed(request);
124+
if (response.isSuccessful()) {
125+
return response;
126+
}
127+
if (!shouldRetry(response)) {
128+
return response;
129+
}
130+
} catch (IOException e) {
131+
if (attempt >= maxRetries) {
132+
throw e;
133+
}
134+
exception = e;
135+
}
136+
if (attempt < maxRetries) {
137+
waitForRetry(attempt);
138+
}
139+
}
140+
141+
if (response != null) {
142+
return response;
143+
}
144+
if (exception != null) {
145+
throw exception;
146+
} else {
147+
throw new IOException("Failed to get a valid response after all retries and no exception was caught.");
148+
}
109149
}
110150

111-
public void setMaxRetry(int maxRetry) {
112-
this.maxRetry = maxRetry;
151+
private boolean shouldRetry(Response response) {
152+
final int code = response.code();
153+
if (code >= 500 && code < 600) {
154+
return true;
155+
}
156+
return RETRYABLE_STATUS_CODES.contains(code);
113157
}
114158

115-
@Override
116-
public Response intercept(Chain chain) throws IOException {
117-
Request request = chain.request();
118-
Response response = chain.proceed(request);
119-
for (int i = 0; i < maxRetry && !response.isSuccessful(); ++i) {
120-
response.close();
121-
response = chain.proceed(request);
159+
private void waitForRetry(int attempt) {
160+
try {
161+
final long delayMs = Math.min(MAX_DELAY_MS, retryIntervalMs * (1L << attempt));
162+
TimeUnit.MILLISECONDS.sleep(delayMs);
163+
} catch (InterruptedException e) {
164+
Thread.currentThread().interrupt();
122165
}
123-
return response;
124166
}
125167
}

0 commit comments

Comments
 (0)