网站首页 > 技术文章 正文
一、前言
HttpClient提供了两种I/O模型:经典的java阻塞I/O模型和基于Java NIO的异步非阻塞事件驱动I/O模型。
Java中的阻塞I/O是一种高效、便捷的I/O模型,非常适合并发连接数量相对适中的高性能应用程序。只要并发连接的数量在1000个以下并且连接大多忙于传输数据,阻塞I/O模型就可以提供最佳的数据吞吐量性能。然而,对于连接大部分时间保持空闲的应用程序,上下文切换的开销可能会变得很大,这时非阻塞I/O模型可能会提供更好的替代方案。
异步I/O模型可能更适合于比较看重资源高效利用、系统可伸缩性、以及可以同时支持更多HTTP连接的场景。
二、HttpClient中的Future
在HttpClient官网Tutorial的高级话题中,我们可以发现其提供了用于异步执行的FutureRequestExecutionService服务类。
使用FutureRequestExecutionService,允许我们发起http调用后,调用函数马上返回(调用线程不会阻塞等到相应结果返回)一个Future对象,然后调用线程可以在需要响应结果的地方调用Future对象的get方法来阻塞等待结果。
使用FutureRequestExecutionService的优点是,我们可以使用多个线程并发调度请求、设置任务超时,或者在不再需要响应时取消它们。
FutureRequestExecutionService其实是用一个HttpRequestFutureTask包装请求,该HttpRequestFutureTask扩展了JDK中的FutureTask。这个类允许我们取消任务、跟踪各种执行指标,如请求持续时间等。
下面我们看一个例子:
01
// 1.创建线程池
02
private static ExecutorService executorService = Executors.newFixedThreadPool(5);
03
04
// 2.创建http回调函数
05
private static final class OkidokiHandler implements ResponseHandler<String> {
06
public String handleResponse(final HttpResponse response) throws ClientProtocolException, IOException {
07
// 2.1处理响应结果
08
return EntityUtils.toString(response.getEntity());
09
}
10
}
11
12
public static void main(String[] args) throws IOException, InterruptedException, ExecutionException {
13
// 3.创建httpclient对象
14
CloseableHttpClient httpclient = HttpClients.createDefault();
15
16
// 4.创建FutureRequestExecutionService实例
17
FutureRequestExecutionService futureRequestExecutionService = new FutureRequestExecutionService(httpclient,
18
executorService);
19
20
// 5.发起调用
21
try {
22
// 5.1请求参数
23
HttpGet httpget1 = new HttpGet("http://127.0.0.1:8080/test1");
24
HttpGet httpget2 = new HttpGet("http://127.0.0.1:8080/test2");
25
// 5.2发起请求,不阻塞,马上返回
26
HttpRequestFutureTask<String> task1 = futureRequestExecutionService.execute(httpget1,
27
HttpClientContext.create(), new OkidokiHandler());
28
29
HttpRequestFutureTask<String> task2 = futureRequestExecutionService.execute(httpget2,
30
HttpClientContext.create(), new OkidokiHandler());
31
32
// 5.3 do somthing
33
34
// 5.4阻塞获取结果
35
String str1 = task1.get();
36
String str2 = task2.get();
37
System.out.println("response:" + str1 + " " + str2);
38
} finally {
39
httpclient.close();
40
}
41
}
如上代码1创建了一个线程池用来作为http异步执行的后台线程,代码2创建了一个http响应结果处理器,用来异步处理http的响应结果。
代码3创建了一个HttpClient对象,代码4创建一个FutureRequestExecutionService,参数1为创建的httpclient对象,参数2为创建的线程池。
代码5则创建2个Get请求参数,然后执行代码5.2发起两个http请求,该调用会马上返回自己对于的HttpRequestFutureTask对象,调用线程也会马上返回,然后调用线程就可以在5.3做其他的事情,最后在需要获取http响应结果的地方,比如代码5.4调用两个future的get()方法来获取结果。
如上基于Future方式,我们可以并发的发起两个http请求,而之前阻塞方式,则是顺序执行的。
但是基于上面Future方式,我们调用线程还是会在代码5.4阻塞等待响应结果,这并不是我们想要的,我们想要的是事件通知,http确实也提供了注册CallBack的方式:
首先我们需要实现FutureCallback接口,用来接收通知:
01
private static final class MyCallback implements FutureCallback<String> {
02
03
public void failed(final Exception ex) {
04
System.out.println(ex.getLocalizedMessage());
05
}
06
07
public void completed(final String result) {
08
System.out.println(result);
09
}
10
11
public void cancelled() {
12
System.out.println("cancelled");
13
}
14
}
然后我们只需要修改代码5.2,使用三个参数的execute方法发起调用:
01
// 5.发起调用
02
try {
03
// 5.1请求参数
04
HttpGet httpget1 = new HttpGet("http://127.0.0.1:8080/test1");
05
HttpGet httpget2 = new HttpGet("http://127.0.0.1:8080/test2");
06
// 5.2发起请求,不阻塞,马上返回
07
HttpRequestFutureTask<String> task1 = futureRequestExecutionService.execute(httpget1,
08
HttpClientContext.create(), new OkidokiHandler(), new MyCallback());
09
10
HttpRequestFutureTask<String> task2 = futureRequestExecutionService.execute(httpget2,
11
HttpClientContext.create(), new OkidokiHandler(), new MyCallback());
12
//main线程休眠10s,避免请求结束前,关闭了链接
13
Thread.sleep(10000);
14
...
如上代码,使用CallBack后,调用线程就得到了彻底解放,就不必再阻塞获取结果了,当http返回结果后,会自动调用我们注册的CallBack。
三、HttpAsyncClient-真正的异步
上面HttpClient提供的CallBack的方式,虽然解放了调用线程,但是并不是真正意义上的异步调用,因为其异步调用的支持是基于我们创建的executorService线程。即:虽然发起http调用后,调用线程马上返回了,但是其内部还是使用executorService中的一个线程阻塞等待响应结果。
HttpAsyncClient则使用Java NIO的异步非阻塞事件驱动I/O模型,实现了真正意义的异步调用,使用HttpAsyncClient我们需要引入其专门的包:
1
<dependency>
2
<groupId>org.apache.httpcomponents</groupId>
3
<artifactId>httpasyncclient</artifactId>
4
<version>4.1.4</version>
5
</dependency>
然后改造后代码如下:
01
// 1.创建CallBack
02
private static final class MyCallback implements FutureCallback<HttpResponse> {
03
04
public void failed(final Exception ex) {
05
System.out.println(ex.getLocalizedMessage());
06
}
07
08
public void completed(final HttpResponse response) {
09
try {
10
System.out.println(EntityUtils.toString(response.getEntity()));
11
} catch (ParseException e) {
12
e.printStackTrace();
13
} catch (IOException e) {
14
e.printStackTrace();
15
}
16
}
17
18
public void cancelled() {
19
System.out.println("cancelled");
20
}
21
}
22
23
public static void main(String[] args) throws IOException, InterruptedException, ExecutionException {
24
// 2.创建异步httpclient对象
25
CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom().build();
26
27
// 3.发起调用
28
try {
29
30
// 3.0启动
31
httpclient.start();
32
// 3.1请求参数
33
HttpGet httpget1 = new HttpGet("http://127.0.0.1:8080/test1");
34
HttpGet httpget2 = new HttpGet("http://127.0.0.1:8080/test2");
35
// 3.2发起请求,不阻塞,马上返回
36
httpclient.execute(httpget1, new MyCallback());
37
httpclient.execute(httpget2, new MyCallback());
38
39
// 3.3休眠10s,避免请求执行完成前,关闭了链接
40
Thread.sleep(10000);
41
} finally {
42
httpclient.close();
43
}
44
}
如上代码1,创建异步回调实现,用于处理Http响应结果。代码2创建了异步HttpClient,代码3.0启动client,代码3.2发起请求。
基于Java NIO的异步,当发起请求后,调用方不会使用任何线程同步等待http服务端的响应结果(少量的NIO线程不算哦,因为其个数固定,并且不随并发请求数量变化),而是会使用少量内存来记录请求信息,以便服务端响应结果回来后,可以找到对应的回调函数进行执行。
原文链接:https://ifeve.com/httpclient-async-call/
猜你喜欢
- 2024-10-10 SpringBoot整合Grpc实现跨语言RPC通讯
- 2024-10-10 RequestMapping属性详解 - SpringMVC高手进阶
- 2024-10-10 《Servlet》第22节:获取ServletContext上下文对象的四种方式
- 2024-10-10 阿里Java二面:说说Spring MVC执行流程及原理?这样聊能吊打面试官
- 2024-10-10 Springboot——用更优雅的方式发HTTP请求(RestTemplate详解)
- 2024-10-10 JavaServlet生命周期、HttpServletRequest和HttpServletResponse
- 2024-10-10 关于RESTful一些注意事项和自己整理的接口开发规范
- 2024-10-10 java版gRPC实战之二:服务发布和调用
- 2024-10-10 Servlet 点击计数器 点击计数在线
- 2024-10-10 Java开发架构篇:初识领域驱动设计DDD落地
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)