异步请求CloseableHttpAsyncClient的使用
概述
有时候,我们需要把一些没用影响业务逻辑的http请求改成异步请求,httpclient在4.0后提供的api CloseableHttpAsyncClient
可以实现这种功能
提供AIO操作的api
网络调用类型
传统BIO(Blocking IO)
- 客户端有连接请求时服务器端就需要启动一个线程进行处理,
- 如果这个连接不做任何事情会造成不必要的线程开销(可以通过线程池机制改善)
NIO(Not-Blocking IO)
同步非阻塞
式IO:服务器实现模式为一个请求一个线程
- 即客户端发送的连接请求都会注册到多路复用器上,
- 多路复用器轮询到
连接有I/O请求时
才启动一个线程进行处理。
AIO(NIO.2)
异步非阻塞
式IO:服务器实现模式为一个有效请求一个线程
,
- 客户端的I/O请求都是由OS先完成了,再通知服务器应用去启动线程进行处理
使用
pom引用
httpclient和httpasyncclient是两个maven包,前者提供同步api,后者提供异步api
<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.2</version>
</dependency>
<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpasyncclient</artifactId><version>4.1.2</version>
</dependency>
创建异步客户端
一定要设置setMaxConnTotal(并发连接最大数量)和setMaxConnPerRoute(每个域名最大并发连接数量)
,默认值限制的并发连接数量太小了,导致event loop线程根本跑不满,吞吐上不去
CloseableHttpAsyncClient asyncClient = HttpAsyncClientBuilder.create().setMaxConnTotal(1000).setMaxConnPerRoute(1000).build();
启动异步线程
// 启动异步I/O线程
asyncClient.start();
构造URI
URIBuilder说明:
- URIBuilder既可以造出一个不可变的URI,又可以兼顾N种参数,示例如下:
/ http://www.google.com/search?aq=f URI uri = new URIBuilder().setScheme("http").setHost("www.google.com").setPath("/search").setParameter("aq", "f").build();
构造URI:
URI uri = new URIBuilder("https://www.google.com/search/aq")
.addParameter("aq", "f")
.build();
构造Get请求
URIBuilder
HttpGet request = new HttpGet(uri);
直接使用
String url = "https://www.google.com/search?aq=f";
HttpPost httpPost = new HttpPost(url);
构造Post请求
URIBuilder
HttpPost request = new HttpPost(uri);
直接使用
String url = "https://www.google.com/search?aq=f";
HttpPost httpPost = new HttpPost(url);
开启异步请求
拿着future可以wait等待完成(future原理就是一个mutex+cond)
// 提交请求到异步I/O线程
Future<HttpResponse> future = asyncClient.execute(request, null);
等待获取响应
// 等待异步请求完成
HttpResponse response = future.get();
String content = EntityUtils.toString(response.getEntity(), "utf-8");
System.out.println(content);
基于callback来处理应答
调用者的future.get()其实还是阻塞等待的,如果要做纯异步的高吞吐程序,那就直接基于callback来处理应答
// 提交请求到异步I/O线程,等待callback回调就行,不用返回的future来同步等待了
Future<HttpResponse> future = asyncClient.execute(request, new FutureCallback<HttpResponse>(){@Overridepublic void completed(HttpResponse response) {try {String content = EntityUtils.toString(response.getEntity(), "utf-8"); System.out.println(content); } catch (Exception e) {}}@Overridepublic void failed(Exception ex) {}@Overridepublic void cancelled() {}
});// 等一下请求完成
Thread.sleep(2000);// 关闭Http客户端
asyncClient.close();
几个重要的参数
ConnectTimeout
: 连接超时,连接建立时间,三次握手完成时间。
SocketTimeout
: 请求超时,数据传输过程中数据包之间间隔的最大时间。
ConnectionRequestTimeout
: 使用连接池来管理连接,从连接池获取连接的超时时间。
- 如果按照远超下游处理能力的速度发起请求,httpasyncclient的request队列就会堆积,等到event loop线程终于消费到队列末尾的request时,这个request已经排队很久了,会直接被标记为超时并callback告知失败
所以如果网络调用吞吐量很高的话,要么自己控制好请求发送速率,要么适当调大setConnectionRequestTimeout来允许request被处理之前排队更久
ConnTotal:
连接池中最大连接数;
ConnPerRoute(1000)
:分配给同一个route(路由)最大的并发连接数,route为运行环境机器到目标机器的一条线路
CloseableHttpAsyncClient client = HttpAsyncClients.custom().setConnectionManager(connManager).setDefaultRequestConfig(requestConfig).build();