> 文章列表 > 异步请求CloseableHttpAsyncClient的使用

异步请求CloseableHttpAsyncClient的使用

异步请求CloseableHttpAsyncClient的使用

概述

有时候,我们需要把一些没用影响业务逻辑的http请求改成异步请求,httpclient在4.0后提供的api CloseableHttpAsyncClient可以实现这种功能

提供AIO操作的api

网络调用类型

传统BIO(Blocking IO)

同步阻塞式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说明:

  1. 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();