通过http做接口调用请求的时候,常常会因为网络抖动或者其他未知原因,造成接口在某个瞬间访问不了。此时需要增加重试机制。刚出来的时候掉接口需要三次重试,由于对httpclient不是很了解。只能在for循环里面对异常经常处理并重新调接口。后来做http服务端的时候,有次debug偶然发现客户端调一次请求,服务端会跳多次debug,后来查阅资料发现httpclient有重试机制。
今天做了个通天塔接口重试的需求,便想起来了httpclient的重试机制。
查了很久资料,也测试了很多次。后来终于成功了。是通过设置httpclient 的retryHandler来实现。
不多说废话,直接贴代码,如下:
/**
* @param isPooled 是否使用连接池
*/
public static CloseableHttpClient getClient(boolean isPooled) {
HttpRequestRetryHandler handler = new HttpRequestRetryHandler() {
@Override
public boolean retryRequest(IOException arg0, int retryTimes, HttpContext arg2) {
if (retryTimes > 2) {
return false;
}
if (arg0 instanceof UnknownHostException || arg0 instanceof ConnectTimeoutException
|| !(arg0 instanceof SSLException) || arg0 instanceof NoHttpResponseException) {
return true;
}
HttpClientContext clientContext = HttpClientContext.adapt(arg2);
HttpRequest request = clientContext.getRequest();
boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
if (idempotent) {
// 如果请求被认为是幂等的,那么就重试。即重复执行不影响程序其他效果的
return true;
}
return false;
}
};
if (isPooled) {
return HttpClients.custom().setConnectionManager(cm).setRetryHandler(handler).build();
}
return HttpClients.createDefault();
}
上面的代码是封装了一个HttpClientUtil的工具类中的获取线程池并设置retry机制的方法。整个HttpClientUtil文件如下:
package com.jd.bingo.yhd.util.http;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.jd.bingo.yhd.util.JsonUtil;
import com.jd.ump.annotation.JProEnum;
import com.jd.ump.annotation.JProfiler;
import org.apache.commons.httpclient.ConnectTimeoutException;
import org.apache.commons.httpclient.NoHttpResponseException;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.net.ssl.SSLException;
import javax.servlet.http.HttpServletRequest;
@Component
public class HttpClientUtil {
private static Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);
private static final RequestConfig REQUEST_CONFIG_TIME_OUT = RequestConfig.custom()
.setSocketTimeout(5000)
.setConnectTimeout(5000)
.setConnectionRequestTimeout(5000)
.build();
private static PoolingHttpClientConnectionManager cm = null;
@PostConstruct
public void init() {
cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(1000);
cm.setDefaultMaxPerRoute(1000);
}
/**
* @param isPooled 是否使用连接池
*/
public static CloseableHttpClient getClient(boolean isPooled) {
HttpRequestRetryHandler handler = new HttpRequestRetryHandler() {
@Override
public boolean retryRequest(IOException arg0, int retryTimes, HttpContext arg2) {
if (retryTimes > 2) {
return false;
}
if (arg0 instanceof UnknownHostException || arg0 instanceof ConnectTimeoutException
|| !(arg0 instanceof SSLException) || arg0 instanceof NoHttpResponseException) {
return true;
}
HttpClientContext clientContext = HttpClientContext.adapt(arg2);
HttpRequest request = clientContext.getRequest();
boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
if (idempotent) {
// 如果请求被认为是幂等的,那么就重试。即重复执行不影响程序其他效果的
return true;
}
return false;
}
};
if (isPooled) {
return HttpClients.custom().setConnectionManager(cm).setRetryHandler(handler).build();
}
return HttpClients.createDefault();
}
public static final String doPostWithRequest(String path, HttpServletRequest request) {
Enumeration params = request.getParameterNames();
List nameValuePairs = Lists.newArrayList();
while (params.hasMoreElements()) {
String paramName = (String) params.nextElement();
nameValuePairs.add(new BasicNameValuePair(paramName, request.getParameter(paramName)));
}
HttpPost httpPost = new HttpPost(path);
httpPost.setConfig(REQUEST_CONFIG_TIME_OUT);
try {
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
return execReq(httpPost);
} catch (UnsupportedEncodingException e) {
logger.error("do post error: ", e);
}
return "";
}
public static final String doPost(String path, Map params) {
logger.debug("doPost from " + path, params);
HttpPost httpPost = new HttpPost(path);
httpPost.setConfig(REQUEST_CONFIG_TIME_OUT);
try {
httpPost.setEntity(new UrlEncodedFormEntity(createParams(params)));
String bodyAsString = execReq(httpPost);
if (bodyAsString != null) return bodyAsString;
} catch (UnsupportedEncodingException e) {
logger.error(e.getMessage(), e);
}
return errorResponse("-2", "unknown error");
}
private static String execReq(HttpPost httpPost) {
try {
CloseableHttpResponse response = getClient(true).execute(httpPost);
if (response != null) {
try {
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
return EntityUtils.toString(response.getEntity());
} else {
return errorResponse(String.valueOf(response.getStatusLine().getStatusCode()),
"http post request error: " + httpPost.getURI());
}
} finally {
EntityUtils.consume(response.getEntity());
}
} else {
return errorResponse("-1", "response is null");
}
} catch (IOException e) {
logger.error("doPost error: ", e);
}
return errorResponse("-3", "unknown error");
}
private static String errorResponse(String code, String msg) {
return JsonUtil.toJsonStr(ImmutableMap.of("code", code, "msg", msg));
}
private static List createParams(Map params) {
List nameValuePairs = Lists.newArrayList();
for (Map.Entry entry : params.entrySet()) {
nameValuePairs.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue())));
}
return nameValuePairs;
}
}
httpclient的调用如下:
String bingoPortalResStr = HttpClientUtil.doPostWithRequest(path, request);
参考:https://www.jianshu.com/p/481cbfcd3f13