最近在业务中接入了一个第三方的接口,第三方为了提高服务的可用性提供了多个地址供外部服务调用,所以需要实现在请求某一个地址不可用时自动切换到另一个地址并重试的功能。由于业务中使用 OkHttp,所以直接用 OkHttp 的自定义拦截器实现。
1.在 application.properties 中配置外部服务地址,多个地址用英文半角逗号隔开
xxx.api.addr=http://www.baidu.com,http://www.google.com
2.自定义 Intercept
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
/**
* okHttp拦截器,主要用于拦截失败请求并重试
* @author hellohuan
* @date 2020/1/11 9:38
*/
public class RetryAndChangeUrlInterceptor implements Interceptor {
private static final Logger LOG = LoggerFactory.getLogger(RetryAndChangeUrlInterceptor.class);
/**
* 默认重试3次
*/
private int retryCount;
/**
* 默认url
*/
private String firstAddr;
/**
* url地址池
*/
private List<String> address;
public RetryAndChangeUrlInterceptor(String firstUrl, List<String> urls) {
this.firstAddr = firstUrl;
this.address = urls;
this.retryCount = 3;
}
public RetryAndChangeUrlInterceptor(String firstUrl, List<String> urls, int tryCount) {
this.firstAddr = firstUrl;
this.address = urls;
this.retryCount = tryCount;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// try the request
Response response = doRequest(chain, request);
int tryCount = 0;
String url = request.url().toString();
while (response == null && tryCount < retryCount) {
String old = url;
// url = switchServer(url);
url = switchServer(url, tryCount % address.size(), (tryCount + 1) % address.size());
Request newRequest = request.newBuilder().url(url).build();
LOG.warn("Http Request is failed , Request index - {} , Old url - {} , New url = {}", tryCount, old, url);
tryCount++;
// retry the request
response = doRequest(chain, newRequest);
}
if (response == null) {
throw new IOException();
}
return response;
}
private Response doRequest(Chain chain, Request request) {
Response response = null;
try {
response = chain.proceed(request);
} catch (Exception e) {
}
return response;
}
/**
* 单向取址
* @param url
* @return
*/
private String switchServer(String url) {
String newUrlString = url;
if (url.contains(firstAddr)) {
for (String server : address) {
if (!firstAddr.equals(server)) {
newUrlString = url.replace(firstAddr, server);
break;
}
}
} else {
for (String server : address) {
if (url.contains(server)) {
newUrlString = url.replace(server, firstAddr);
break;
}
}
}
return newUrlString;
}
/**
* 循环取址
* @param url
* @param oldIndex
* @param newIndex
* @return
*/
private String switchServer(String url, int oldIndex, int newIndex) {
String newUrlString = url;
if (newIndex < address.size() || oldIndex < address.size()){
String oldAddr = address.get(oldIndex);
String newAddr = address.get(newIndex);
newUrlString = url.replace(oldAddr, newAddr);
}
return newUrlString;
}
}
3.在创建 HttpClient 时添加 intercept
public XXXHttpClient(@Value("#{'${xxx.api.addr}'.split(',')}")List<String> urls) {
if (urls.isEmpty()){
LOG.error("xxx api 地址为空,请检查配置文件");
System.exit(0);
}
String firstUrl = urls.get(0);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new RetryAndChangeUrlInterceptor(firstUrl, urls, urls.size()-1))
.retryOnConnectionFailure(true)
.connectTimeout(5, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS).build();
this.client = client;
}
这样,就实现了第三方接口的多地址配置和请求失败自动切换地址重试。
#全文毕
欢迎关注微信公众号:Javall咖啡屋
每天更新各种互联网技术(前后端、数据库、中间件、设计模式、数据结构、算法)学习心得体会