背景: 最新项目需求调用 http 接口,所以打算使用最新的 httpClient 客户端写一个工具类,写好了以后在实际应用过程中遇到了一些问题,因为数据量还算
大,每次处理大概要处理 600-700 次请求,平均算下来大概需要 20 分钟,这个速度虽然是跑在定时任务中的,但是也是不能忍受的,所以有了这个博客.
1. 首先想到的解决办法就是多线程发请求了,但是这个有坑,最后会在结果处说明.
2. 代码方面如下
ExecutorService executor = Executors.newFixedThreadPool(5);
FutureTask < Order > future;
for (TransactionRecord record: list) {
final String orderNo = record.getOrderNo();
future = new FutureTask < Order > (new OrderTask(orderNo));
executor.submit(future);
futureList.add(future);
}
class OrderTask implements Callable < Order > {
private String orderNo;
public OrderTask(String orderNo) {
this.orderNo = orderNo;
}@Override public Order call() throws Exception {
Order order = orderService.getOrder(orderNo); //在getOrder中直接调用下面的我封装的http工具类
return order;
}
}
这是一段很简单的多线程代码,但是其中有一个坑需要大家注意的,不要在上面的循环中直接调用 future.get() 方法,如果直接调用的话就直接变成阻塞的了,和单线程
就没有区别了,可以自己写一个 demo 测试一下效率.
3.http 方面的代码,可以全部贴出来,如下
/**
* get
*/
public static HttpResultEntry doPost(String url, Map < String, String > params, String charset) throws Exception {
HttpResultEntry resultEntry = new HttpResultEntry(); //自定义返回结果
CloseableHttpResponse response = null; //返回结果,释放链接
List < NameValuePair > pairs = new ArrayList < >();; //参数
if (StringUtils.isBlank(url)) {
resultEntry.setMsg("请求地址异常");
resultEntry.setStatus(404);
resultEntry.setData("");
return resultEntry;
}
try {
if (params != null && !params.isEmpty()) {
for (Map.Entry < String, String > entry: params.entrySet()) {
String value = entry.getValue();
if (value != null) {
pairs.add(new BasicNameValuePair(entry.getKey(), value));
}
}
}
HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(new UrlEncodedFormEntity(pairs, charset));
response = httpClient.execute(httpPost); //建立链接得到返回结果
int statusCode = response.getStatusLine().getStatusCode(); //返回的结果码
if (statusCode != 200) {
httpPost.abort();
resultEntry.setMsg("请求异常");
resultEntry.setStatus(statusCode);
resultEntry.setData("");
LOGGER.info("返回异常:{}", resultEntry);
return resultEntry;
}
HttpEntity httpEntity = response.getEntity();
String result = null;
if (httpEntity == null) {
resultEntry.setMsg("返回结果异常");
resultEntry.setStatus(statusCode);
resultEntry.setData("");
return resultEntry;
} else {
result = EntityUtils.toString(httpEntity, charset);
}
resultEntry.setMsg("请求正常");
resultEntry.setStatus(statusCode);
resultEntry.setData(result);
EntityUtils.consume(httpEntity); //按照官方文档的说法:二者都释放了才可以正常的释放链接
response.close();
return resultEntry;
} catch(Exception e) {
LOGGER.error("请求错误:{},错误信息:{}", e.getMessage(), e);
throw new Exception("HTTP请求异常");
} finally {
if (response != null) {
try {
response.close();
} catch(IOException e) {
LOGGER.error("关闭流异常:{},错误信息:{}", e.getMessage(), e);
}
}
}
}
/**
* post
*/
public static HttpResultEntry doGet(String url, Map < String, String > params, String charset) throws Exception {
HttpResultEntry resultEntry = new HttpResultEntry(); //自定义返回结果
CloseableHttpResponse response = null; //返回结果,释放链接
List < NameValuePair > pairs = new ArrayList < >(); //参数
if (StringUtils.isBlank(url)) {
resultEntry.setMsg("请求地址异常");
resultEntry.setStatus(404);
resultEntry.setData("");
return resultEntry;
}
try {
if (params != null && !params.isEmpty()) {
for (Map.Entry < String, String > entry: params.entrySet()) {
String value = entry.getValue();
if (value != null) {
pairs.add(new BasicNameValuePair(entry.getKey(), value));
}
}
url += "?" + EntityUtils.toString(new UrlEncodedFormEntity(pairs, charset));
}
HttpGet httpGet = new HttpGet(url);
response = httpClient.execute(httpGet); //建立链接得到返回结果
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != 200) {
httpGet.abort();
resultEntry.setMsg("请求异常");
resultEntry.setStatus(statusCode);
resultEntry.setData("");
LOGGER.info("返回异常:{}", resultEntry);
return resultEntry;
}
HttpEntity httpEntity = response.getEntity();
String result = null;
if (httpEntity == null) {
resultEntry.setMsg("返回结果异常");
resultEntry.setStatus(statusCode);
resultEntry.setData("");
return resultEntry;
} else {
result = EntityUtils.toString(httpEntity, charset);
}
resultEntry.setMsg("请求正常");
resultEntry.setStatus(statusCode);
resultEntry.setData(result);
EntityUtils.consume(httpEntity); //按照官方文档的说法:二者都释放了才可以正常的释放链接
response.close();
return resultEntry;
} catch(Exception e) {
LOGGER.error("请求错误:{},错误信息:{}", e.getMessage(), e);
throw new Exception("HTTP请求异常");
} finally {
if (response != null) {
try {
response.close();
} catch(IOException e) {
LOGGER.error("关闭流异常:{},错误信息:{}", e.getMessage(), e);
}
}
}
}
使用的 http 连接池,连接池的代码很简单就不粘贴了,首先使用的时候一定要注意最后的释放工作,必须把 httpEntry 和 respose 都释放掉,按照官方文档的说法,只有这样才是真的释放了链接的,也就是这个链接才可以被复用.
总结: 需要特别注意的是,访问别人的 http 接口的时候一定不要开太多的线程,免得把别人的接口搞挂了,想我就的到了教训,我在访问一个 http 的接口的时候开了一百个线程,666 次请求跑了 3.7 秒左右,是很快我也很开心,然后那边数据库受不了压力了,导致报警最后直接开了白名单,尴尬了,所以使用这些的时候一定要考虑这些,开三五个就够了,另外如果开太多线程的话 tomcat 服务器有可能假死也,不要这么干!
来源: http://www.cnblogs.com/wscit/p/5768447.html