背景:最新項目需求調用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服務器有可能假死也,不要這么干!
