[备忘] apache httpasyncclient基本用法
最近公司内技术分享有点密,快2周没更博客了,这次简单水一下java的http库使用,方便回查。
依赖
httpclient和httpasyncclient是两个maven包,前者提供同步api,后者提供异步api(future和callback都支持)。
1 2 3 4 5 |
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpasyncclient</artifactId> <version>4.1.4</version> </dependency> |
基本用法
创建异步客户端,一定要设置setMaxConnTotal(并发连接最大数量)和setMaxConnPerRoute(每个域名最大并发连接数量),默认值限制的并发连接数量太小了,导致event loop线程根本跑不满,吞吐上不去。(追了一下源码,默认event loop的线程池数量与cpu个数一样,所以不用刻意设置)
1 2 |
// 创建异步HTTP客户端 CloseableHttpAsyncClient asyncClient = HttpAsyncClientBuilder.create().setMaxConnTotal(1000).setMaxConnPerRoute(1000).build(); |
必须手动启动帮它启动一下,否则用不了:
1 2 |
// 启动异步I/O线程 asyncClient.start(); |
接着构造URI(方便设置query string):
1 |
URI uri = new URIBuilder("https://api.live.bilibili.com/xlive/web-room/v1/index/getIpInfo").addParameter("rnd", "1").build(); |
接着用这个URI构造一个GET请求(有需要可以给request设置超时之类的,文章末尾会提一下):
1 |
HttpGet request = new HttpGet(uri); |
1 2 |
// 提交请求到异步I/O线程 Future<HttpResponse> future = asyncClient.execute(request, null); |
等待HttpResponse,取出其返回的Body(api里叫做entity),然后利用一个便捷的方法读取字节流并按utf-8解码为String:
1 2 3 4 |
// 等待异步请求完成 HttpResponse response = future.get(); String content = EntityUtils.toString(response.getEntity(), "utf-8"); System.out.println(content); |
POST表单
变化一下request的类型,然后设置一下form就好:
1 2 3 4 5 6 7 |
// 构造请求 URI uri = new URIBuilder("https://api.live.bilibili.com/xlive/web-room/v1/index/getIpInfo").addParameter("rnd", "1").build(); HttpPost request = new HttpPost(uri); // 设置表单body List<NameValuePair> form = new ArrayList<>(); form.add(new BasicNameValuePair("from", "java")); request.setEntity(new UrlEncodedFormEntity(form)); |
Callback方式
虽然请求是丢到event loop线程里异步完成的,但是调用者的future.get()其实还是阻塞等待的,如果要做纯异步的高吞吐程序,那就直接基于callback来处理应答吧(PS:还是Golang协程香):
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 |
// 构造请求 URI uri = new URIBuilder("https://api.live.bilibili.com/xlive/web-room/v1/index/getIpInfo").addParameter("rnd", "1").build(); HttpGet request = new HttpGet(uri); // 提交请求到异步I/O线程,等待callback回调就行,不用返回的future来同步等待了 Future<HttpResponse> future = asyncClient.execute(request, new FutureCallback<HttpResponse>(){ @Override public void completed(HttpResponse response) { try { String content = EntityUtils.toString(response.getEntity(), "utf-8"); System.out.println(content); } catch (Exception e) { } } @Override public void failed(Exception ex) { } @Override public void cancelled() { } }); // 等一下请求完成 Thread.sleep(2000); // 关闭Http客户端 asyncClient.close(); |
最后说个坑
异步I/O虽然可以高吞吐的execute提交请求并等待callback,但是这里面涉及到一个不能过载的问题。
如果按照远超下游处理能力的速度发起请求,httpasyncclient的request队列就会堆积,等到event loop线程终于消费到队列末尾的request时,这个request已经排队很久了,会直接被标记为超时并callback告知失败,这个特性与request的setConnectionRequestTimeout超时配置项有关:
1 2 3 4 5 6 7 |
RequestConfig requestConfig = RequestConfig.custom() .setConnectionRequestTimeout(5000) // request从创建到被eventloop消费到的最大时间,如果追求高吞吐而导致延迟高,这个排队时间要给大一些 .setConnectTimeout(1000) // socket connect的超时 .setSocketTimeout(1500) // connect后整个http请求应答总时间 .build(); HttpGet request = new HttpGet(uri); request.setConfig(requestConfig); |
所以如果网络调用吞吐量很高的话,要么自己控制好请求发送速率,要么适当调大setConnectionRequestTimeout来允许request被处理之前排队更久。
如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~
