异步httpclient(httpasyncclient)的使用与总结
参考:异步httpclient(httpasyncclient)的使用与总结
1. 前言
应用层的网络模型有同步与异步。同步意味当前线程是阻塞的,只有本次请求完成后才能进行下一次请求;异步意味着所有的请求可以同时塞入缓冲区,不阻塞当前的线程;
httpclient在4.x之后开始提供基于nio的异步版本httpasyncclient,httpasyncclient借助了Java并发库和nio进行封装(虽说NIO是同步非阻塞IO,但是HttpAsyncClient提供了回调的机制,与netty类似,所以可以模拟类似于AIO的效果),其调用方式非常便捷,但是其中也有许多需要注意的地方。
2. pom文件
本文依赖4.1.2,当前最新的客户端版本是4.1.3maven repository 地址
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.5</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore-nio</artifactId>
<version>4.4.5</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>
<version>4.1.2</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
3. 简单的实例
public class TestHttpClient {
public static void main(String[] args){
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(50000)
.setSocketTimeout(50000)
.setConnectionRequestTimeout(1000)
.build();
//配置io线程
IOReactorConfig ioReactorConfig = IOReactorConfig.custom().
setIoThreadCount(Runtime.getRuntime().availableProcessors())
.setSoKeepAlive(true)
.build();
//设置连接池大小
ConnectingIOReactor ioReactor=null;
try {
ioReactor = new DefaultConnectingIOReactor(ioReactorConfig);
} catch (IOReactorException e) {
e.printStackTrace();
}
PoolingNHttpClientConnectionManager connManager = new PoolingNHttpClientConnectionManager(ioReactor);
connManager.setMaxTotal(100);
connManager.setDefaultMaxPerRoute(100);
final CloseableHttpAsyncClient client = HttpAsyncClients.custom().
setConnectionManager(connManager)
.setDefaultRequestConfig(requestConfig)
.build();
//构造请求
String url = "http://127.0.0.1:9200/_bulk";
HttpPost httpPost = new HttpPost(url);
StringEntity entity = null;
try {
String a = "{ \"index\": { \"_index\": \"test\", \"_type\": \"test\"} }\n" +
"{\"name\": \"上海\",\"age\":33}\n";
entity = new StringEntity(a);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
httpPost.setEntity(entity);
//start
client.start();
//异步请求
client.execute(httpPost, new Back());
while(true){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class Back implements FutureCallback<HttpResponse>{
private long start = System.currentTimeMillis();
Back(){
}
public void completed(HttpResponse httpResponse) {
try {
System.out.println("cost is:"+(System.currentTimeMillis()-start)+":"+EntityUtils.toString(httpResponse.getEntity()));
} catch (IOException e) {
e.printStackTrace();
}
}
public void failed(Exception e) {
System.err.println(" cost is:"+(System.currentTimeMillis()-start)+":"+e);
}
public void cancelled() {
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
4. 几个重要的参数
4.1 TimeOut(3个)的设置
ConnectTimeout : 连接超时,连接建立时间,三次握手完成时间。
SocketTimeout : 请求超时,数据传输过程中数据包之间间隔的最大时间。
ConnectionRequestTimeout : 使用连接池来管理连接,从连接池获取连接的超时时间。
在实际项目开发过程中,这三个值可根据具体情况设置。
(1) 下面针对ConnectionRequestTimeout的情况进行分析
实验条件:设置连接池最大连接数为1,每一个异步请求从开始到回调的执行时间在100ms以上;
实验过程:连续发送2次请求
public class TestHttpClient {
public static void main(String[] args){
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(50000)
.setSocketTimeout(50000)
.setConnectionRequestTimeout(10)//设置为10ms
.build();
//配置io线程
IOReactorConfig ioReactorConfig = IOReactorConfig.custom().
setIoThreadCount(Runtime.getRuntime().availableProcessors())
.setSoKeepAlive(true)
.build();
//设置连接池大小
ConnectingIOReactor ioReactor=null;
try {
ioReactor = new DefaultConnectingIOReactor(ioReactorConfig);
} catch (IOReactorException e) {
e.printStackTrace();
}
PoolingNHttpClientConnectionManager connManager = new PoolingNHttpClientConnectionManager(ioReactor);
connManager.setMaxTotal(1);//最大连接数设置1
connManager.setDefaultMaxPerRoute(1);//per route最大连接数设置1
final CloseableHttpAsyncClient client = HttpAsyncClients.custom().
setConnectionManager(connManager)
.setDefaultRequestConfig(requestConfig)
.build();
//构造请求
String url = "http://127.0.0.1:9200/_bulk";
List<HttpPost> list = new ArrayList<HttpPost>();
for(int i=0;i<2;i++){
HttpPost httpPost = new HttpPost(url);
StringEntity entity = null;
try {
String a = "{ \"index\": { \"_index\": \"test\", \"_type\": \"test\"} }\n" +
"{\"name\": \"上海\",\"age\":33}\n";
entity = new StringEntity(a);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
httpPost.setEntity(entity);
list.add(httpPost);
}
client.start();
for(int i=0;i<2;i++){
client.execute(list.get(i), new Back());
}
while(true){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class Back implements FutureCallback<HttpResponse>{
private long start = System.currentTimeMillis();
Back(){
}
public void completed(HttpResponse httpResponse) {
try {
System.out.println("cost is:"+(System.currentTimeMillis()-start)+":"+EntityUtils.toString(httpResponse.getEntity()));
} catch (IOException e) {
e.printStackTrace();
}
}
public void failed(Exception e) {
e.printStackTrace();
System.err.println(" cost is:"+(System.currentTimeMillis()-start)+":"+e);
}
public void cancelled() {
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
实验结果 :
第一次请求执行时间在200ms左右
第二请求回调直接抛出TimeOutException
java.util.concurrent.TimeoutException
at org.apache.http.nio.pool.AbstractNIOConnPool.processPendingRequest(AbstractNIOConnPool.java:364)
at org.apache.http.nio.pool.AbstractNIOConnPool.processNextPendingRequest(AbstractNIOConnPool.java:344)
at org.apache.http.nio.pool.AbstractNIOConnPool.release(AbstractNIOConnPool.java:318)
at org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager.releaseConnection(PoolingNHttpClientConnectionManager.java:303)
at org.apache.http.impl.nio.client.AbstractClientExchangeHandler.releaseConnection(AbstractClientExchangeHandler.java:239)
at org.apache.http.impl.nio.client.MainClientExec.responseCompleted(MainClientExec.java:387)
at org.apache.http.impl.nio.client.DefaultClientExchangeHandlerImpl.responseCompleted(DefaultClientExchangeHandlerImpl.java:168)
at org.apache.http.nio.protocol.HttpAsyncRequestExecutor.processResponse(HttpAsyncRequestExecutor.java:436)
at org.apache.http.nio.protocol.HttpAsyncRequestExecutor.inputReady(HttpAsyncRequestExecutor.java:326)
at org.apache.http.impl.nio.DefaultNHttpClientConnection.consumeInput(DefaultNHttpClientConnection.java:265)
at org.apache.http.impl.nio.client.InternalIODispatch.onInputReady(InternalIODispatch.java:81)
at org.apache.http.impl.nio.client.InternalIODispatch.onInputReady(InternalIODispatch.java:39)
at org.apache.http.impl.nio.reactor.AbstractIODispatch.inputReady(AbstractIODispatch.java:114)
at org.apache.http.impl.nio.reactor.BaseIOReactor.readable(BaseIOReactor.java:162)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.processEvent(AbstractIOReactor.java:337)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.processEvents(AbstractIOReactor.java:315)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.execute(AbstractIOReactor.java:276)
at org.apache.http.impl.nio.reactor.BaseIOReactor.execute(BaseIOReactor.java:104)
at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor$Worker.run(AbstractMultiworkerIOReactor.java:588)
at java.lang.Thread.run(Thread.java:745)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
结果分析:由于连接池大小是1,第一次请求执行后连接被占用(时间在100ms),第二次请求在规定的时间内无法获取连接,于是直接连接获取的TimeOutException
(2) 修改ConnectionRequestTimeout
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(50000)
.setSocketTimeout(50000)
.setConnectionRequestTimeout(1000)//设置为1000ms
.build();
1
2
3
4
5
上述两次请求正常执行。
下面进一步看一下代码中抛异常的地方:
从上面的代码中可以看到如果要设置永不ConnectionRequestTimeout,只需要将ConnectionRequestTimeout设置为小于0即可,当然后这种设置一定要慎用, 如果处理不当,请求堆积会导致OOM。
4.2 连接池大小的设置
ConnTotal:连接池中最大连接数;
ConnPerRoute(1000):分配给同一个route(路由)最大的并发连接数,route为运行环境机器到目标机器的一条线路,举例来说,我们使用HttpClient的实现来分别请求 www.baidu.com 的资源和 www.bing.com 的资源那么他就会产生两个route;
对于上述的实验,在一定程度上可以通过增大最大连接数来解决ConnectionRequestTimeout的问题!
后续:本文重点在于使用,后续会对源码进行分析与解读
HttpAsyncClient 的简单使用
下载地址:http://hc.apache.org/downloads.cgi
在NetBeans中导入以下jar文件:
1:一次请求:

- public static void oneReuest(){
- final CloseableHttpAsyncClient httpClient = HttpAsyncClients.createDefault();
- httpClient.start();
- final HttpGet request = new HttpGet("http://www.apache.org/");
- final Future future = httpClient.execute(request, null);
- try {
- HttpResponse response = (HttpResponse) future.get();
- System.out.println("Response:" + response.getStatusLine());
- System.out.println("Shutting down");
- } catch (Exception ex) {
- Logger.getLogger(Httpasyncclient.class.getName()).log(Level.SEVERE, null, ex);
- }finally{
- try {
- httpClient.close();
- } catch (IOException ex) {
- Logger.getLogger(Httpasyncclient.class.getName()).log(Level.SEVERE, null, ex);
- }
- }
- System.out.println("执行完毕");
- }

2:多次异步请求:

- public static void moreRequest(){
- final RequestConfig requestConfitg = RequestConfig.custom()
- .setSocketTimeout(3000)
- .setConnectTimeout(3000).build();
- final CloseableHttpAsyncClient httpClient = HttpAsyncClients.custom()
- .setDefaultRequestConfig(requestConfitg)
- .build();
- httpClient.start();
- final HttpGet[] requests = new HttpGet[]{
- new HttpGet("http://www.apache.org/"),
- new HttpGet("http://www.baidu.com/"),
- new HttpGet("http://www.oschina.net/")
- };
- final CountDownLatch latch = new CountDownLatch(requests.length);
- for(final HttpGet request: requests){
- httpClient.execute(request, new FutureCallback(){
- @Override
- public void completed(Object obj) {
- final HttpResponse response = (HttpResponse)obj;
- latch.countDown();
- System.out.println(request.getRequestLine() + "->" + response.getStatusLine());
- }
- @Override
- public void failed(Exception excptn) {
- latch.countDown();
- System.out.println(request.getRequestLine() + "->" + excptn);
- }
- @Override
- public void cancelled() {
- latch.countDown();
- System.out.println(request.getRequestLine() + "cancelled");
- }
- });
- }
- try {
- latch.await();
- System.out.println("Shutting Down");
- } catch (InterruptedException ex) {
- Logger.getLogger(Httpasyncclient.class.getName()).log(Level.SEVERE, null, ex);
- }finally{
- try {
- httpClient.close();
- } catch (IOException ex) {
- Logger.getLogger(Httpasyncclient.class.getName()).log(Level.SEVERE, null, ex);
- }
- }
- System.out.println("Finish!");
- }

运行结果:

- run:
- GET http://www.baidu.com/ HTTP/1.1->HTTP/1.1 200 OK
- GET http://www.oschina.net/ HTTP/1.1->HTTP/1.1 200 OK
- GET http://www.apache.org/ HTTP/1.1->HTTP/1.1 200 OK
- Shutting Down
- Finish!
- 成功构建 (总时间: 2 秒)
- 可以看出是异步执行的!不是按照我们传入的URL参数顺序执行的!

篇提到了高性能处理的关键是异步,而我们当中许多人依旧在使用同步模式的HttpClient访问第三方Web资源,我认为原因之一是:异步的HttpClient诞生较晚,许多人不知道;另外也可能是大多数Web程序其实不在意这点性能损失了。
而要自己实现一个异步的HttpClient则比较困难,通常都是自己开一个新的工作线程,利用HttpClient的同步去访问,完成后再回调这种形式,这样做其实不是真正的异步,因为依旧会有一个线程处于阻塞中,等待着第三方Web资源的返回。
而如今访问第三方Web资源的情景越来越多,最典型就是使用第三方登录平台,如QQ或微信等,我们需要访问腾讯的服务器去验证登录者的身份,根据我的经验,这个过程可能会阻塞好几秒钟,可看作是一个“长时间调用”,所以最好要使用异步方式。
OK,废话少说,要使用异步的HttpClient,请Maven中带上:
- <dependency>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>httpasyncclient</artifactId>
- <version>4.1.1</version>
- </dependency>
接下来是一个完整的Demo代码:

- import org.apache.http.HttpResponse;
- import org.apache.http.client.methods.HttpGet;
- import org.apache.http.concurrent.FutureCallback;
- import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
- import org.apache.http.impl.nio.client.HttpAsyncClients;
- import org.apache.http.util.EntityUtils;
- import java.io.IOException;
- import java.util.concurrent.CountDownLatch;
- public class Main {
- public static void main(String[] argv) {
- CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault();
- httpclient.start();
- final CountDownLatch latch = new CountDownLatch(1);
- final HttpGet request = new HttpGet("https://www.alipay.com/");
- System.out.println(" caller thread id is : " + Thread.currentThread().getId());
- httpclient.execute(request, new FutureCallback<HttpResponse>() {
- public void completed(final HttpResponse response) {
- latch.countDown();
- System.out.println(" callback thread id is : " + Thread.currentThread().getId());
- System.out.println(request.getRequestLine() + "->" + response.getStatusLine());
- try {
- String content = EntityUtils.toString(response.getEntity(), "UTF-8");
- System.out.println(" response content is : " + content);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- public void failed(final Exception ex) {
- latch.countDown();
- System.out.println(request.getRequestLine() + "->" + ex);
- System.out.println(" callback thread id is : " + Thread.currentThread().getId());
- }
- public void cancelled() {
- latch.countDown();
- System.out.println(request.getRequestLine() + " cancelled");
- System.out.println(" callback thread id is : " + Thread.currentThread().getId());
- }
- });
- try {
- latch.await();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- try {
- httpclient.close();
- } catch (IOException ignore) {
- }
- }
- }

呃……代码很简单,好像也没什么好说的了,稍作封装就可以实现如“getJson()”这样的方法。
也许你还注意到了,这个HttpClient跟同步的版本一样,直接支持https,但如果网站的证书是自签的,默认还是不行的,解决方法当然有,但代码有些麻烦,我觉得还不如直接买张证书来得简单,如果网站是你管的话。
异步httpclient(httpasyncclient)的使用与总结的更多相关文章
- 异步HttpClient大量请求
由于项目中有用到HttpClient异步发送大量http请求,所以做已记录 思路:使用HttpClient连接池,多线程 public class HttpAsyncClient { private ...
- android 学习随笔十二(网络:使用异步HttpClient框架)
使用异步HttpClient框架发送get.post请求 在https://github.com/ 搜索 asyn-http https://github.com/search?utf8=✓& ...
- 使用异步httpclient框架做get,post提交数据
1.将异步httpclient框架导入 下载地址:http://download.csdn.net/detail/sinat_32804317/9555641 2.代码实现 public class ...
- Java的异步HttpClient
上篇提到了高性能处理的关键是异步,而我们当中许多人依旧在使用同步模式的HttpClient访问第三方Web资源,我认为原因之一是:异步的HttpClient诞生较晚,许多人不知道:另外也可能是大多数W ...
- httpClient 深入浅出~
本文偏重使用,简单讲述httpclient,其实在网络编程中,基于java的实现几乎都是包装了socket的通信,然后来模拟各种各样的协议:httpclient其实就是模拟浏览器发起想服务器端的请求, ...
- [转]Tornado get/post请求异步处理框架分析
转自: http://m.blog.csdn.net/blog/joeyon/41956027 首先说下环境,python2.7,Tornado3.0 然后本文讨论的不是Tornado底层如何用epo ...
- flink---实时项目----day03---1.练习讲解(全局参数,数据以parquet格式写入hdfs中) 2 异步查询 3 BroadcastState
1 练习讲解(此处自己没跑通,以后debug) 题目见flink---实时项目---day02 kafka中的数据,见day02的文档 GeoUtils package cn._51doit.flin ...
- 用IntelliJ IDEA创建Gradle项目简单入门
Gradle和Maven一样,是Java用得最多的构建工具之一,在Maven之前,解决jar包引用的问题真是令人抓狂,有了Maven后日子就好过起来了,而现在又有了Gradle,Maven有的功能它都 ...
- 【转】异步的AsyncHttpClient使用详解
http://blog.csdn.net/angjunqiang/article/details/55259170 背景 前面的一篇文章[同步的HttpClient使用详解]中,提到了服务端通进行网络 ...
随机推荐
- centos7 mysql5.7安装
环境:centos7.4 mysql:5.7 安装方式yum安装: wget https://dev.mysql.com/get/mysql57-community-release-el7-9.noa ...
- node.js之Cookie
最近还是用node.js比较多,今天正好遇见一个问题,还是关于Cookie. node.js中如何实现cookie(以express框架为例): "use strict"; var ...
- C语言中指针变量的加减运算
1.指针变量中存放的是地址值,也就是一个数字地址,例如某指针变量中的值是0x20000000,表示表示此指针变量存放的是内存中位于0x20000000地方的内存地址.指针变量可以加减,但是只能与整型数 ...
- Feature Extractor[VGG]
0. 背景 Karen Simonyan等人在2014年参加Imagenet挑战赛的时候提出的深度卷积神经网络.作者通过对2013年的ILSVRC中最好的深度神经网络模型(他们最初的对应模型都是ale ...
- Shiro核心概述
0.写在前面的话 最近在考虑权限相关的东西,于是就找到了Shiro,开涛老师的Shiro教程博客(<跟我学Shiro>)写得实在很好还带所有源码,所以我也就没有自己再总结各个阶段的笔记,只 ...
- Vue-发布订阅机制(bus)实现非父子组件的传值
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- odoo11 安装python ldap
最近在研究odoo11使用ldap登录的问题,本来自己想着怎么开发,无意间在odoo11代码中看到auth_ldap的模块,原来框架已经考虑到了这个,简单研究了代码之后,理解了其大概的登录处理过程,此 ...
- Cat8 八类网线是什么?与Cat5、Cat6、Cat7网线的区别?
若您身处于网络通信行业,相信您应该了解一些以太网线缆,比如说超五类网线.六类网线和七类网线等等.但是您知道Cat8 八类网线 是什么吗?它与五类网线.六类/超六类网线及七类/超七类网线有着怎么样的区别 ...
- mysql触发器,视图,游标
什么事触发器: 触发器是一中特殊的存储过程,主要是通过事件来触发而被执行的.它可以强化约束,来维护数据的完整性和一致性,可以跟踪数据库内的操作从而不允许未经许可的更新和变化.可以联级运算.如,某表上的 ...
- 小小知识点(二)——如何修改win10 的C盘中用户下的文件夹名称
1.以管理员身份登录计算机 在win10桌面的开始界面处有个用户头像,点击在里面找到administrator: 如果没有,则需进行如下设置: (1)右键计算机,双击管理,找到如下所示的用户中的adm ...