从JDK11新增HttpClient谈谈非阻塞模型
北京时间 9 月 26 日,Oracle 官方宣布 Java 11 正式发布
一、JDK HTTP Client介绍
JDK11中的17个新特性
JDK11中引入HTTP Client的动机
既有的HttpURLConnection存在许多问题
- 其基类URLConnection当初是设计为支持多协议,但其中大多已经成为非主流(ftp, gopher…)
- API的设计早于HTTP/1.1,过度抽象
- 难以使用,存在许多没有文档化的行为
- 它只支持阻塞模式(每个请求/响应占用一个线程)
HTTP Client发展史
在JDK11 HTTP Client出现之前
在此之前,可以使用以下工具作为Http客户端
- JDK HttpURLConnection
- Apache HttpClient
- Okhttp
- Spring Rest Template
- Spring Cloud Feign
- 将Jetty用作客户端
- 使用Netty库。还
初探JDK HTTP Client
我们来看一段HTTP Client的常规用法的样例 ——
执行GET请求,然后输出响应体(Response Body)。
HttpClient client = HttpClient.newHttpClient(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://openjdk.java.net/")) .build(); client.sendAsync(request, asString()) .thenApply(HttpResponse::body) .thenAccept(System.out::println) .join();
第一步:创建HttpClient
一般使用JDK 11中的HttpClient的第一步是创建HttpClient对象并进行配置。
- 指定协议(http/1.1或者http/2)
- 转发(redirect)
- 代理(proxy)
- 认证(authenticator)
HttpClient client = HttpClient.newBuilder() .version(Version.HTTP_2) .followRedirects(Redirect.SAME_PROTOCOL) .proxy(ProxySelector.of(new InetSocketAddress("www-proxy.com", 8080))) .authenticator(Authenticator.getDefault()) .build();
第二步:创建HttpRequest
从HttpRequest的builder组建request
- 请求URI
- 请求method(GET, PUT, POST)
- 请求体(request body)
- Timeout
- 请求头(request header)
HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://openjdk.java.net/")) .timeout(Duration.ofMinutes(1)) .header("Content-Type", "application/json") .POST(BodyPublisher.fromFile(Paths.get("file.json"))) .build()
第三步:send
- http client可以用来发送多个http request
- 请求可以被以同步或异步方式发送
1. 同步发送
同步发送API阻塞直到HttpResponse返回
HttpResponse<String> response = client.send(request, BodyHandler.asString()); System.out.println(response.statusCode()); System.out.println(response.body());
2. 异步发送
- 异步发送API立即返回一个CompletableFuture
- 当它完成的时候会获得一个HttpResponse
client.sendAsync(request, BodyHandler.asString()) .thenApply(response -> { System.out.println(response.statusCode()); return response; } ) .thenApply(HttpResponse::body) .thenAccept(System.out::println);
※CompletableFuture是在java8中加入的,支持组合式异步编程
二、从提升单机并发处理能力的技术来看HttpClient的实现细节
提升单机并发处理能力的技术
- 多CPU/多核
- 多线程
- 非阻塞(non-blocking)
Java NIO
Java NIO为Java带来了非阻塞模型。
Lambda表达式
- Lambda表达式可以方便地利用多CPU。
- Lambda表达式让代码更加具有可读性
回调
假设以下代码是一个聊天应用服务器的一部分,该应用以Vert.x框架实现。
(Eclipse Vert.x is a tool-kit for building reactive applications on the JVM.)
向connectHandler方法输入一个Lambda表达式,每当有用户连接到聊天应用时,都会调用该Lambda表达式。这就是一个回调。
这种方式的好处是,应用不必控制线程模型——Vert.x框架为我们管理线程,打理好一切相关复杂性,程序员只考虑和回调就够了。
vertx.createServer() .connectHandler(socket -> { socket.dataHandler(new User(socket, this)); }).listen(10_000);
注意,这种设计里,不共享任何状态。对象之间通过向事件总线发送消息通信,根本不需要在代码中添加锁或使用synchronized关键字。并发编程变得更加简单。
Future
大量的回调会怎样?请看以下伪代码
(1)->{ (2)->{ (3)->{ (4)->{} } } }
大量回调会形成“末日金字塔”。
如何破解? 使用Future
Future1=(1)->{} Future2=(Future1.get())->{} Future3=(Future2.get())->{} Future4=(Future3.get())->{}
但这会造成原本期望的并行处理,变成了串行处理,带来了性能问题。
我们真正需要的是将Future和回调联合起来使用。下面将要讲的CompletableFuture就是结合了Future和回调,其要点是组合不同实例而无需担心末日金字塔问题。
CompletableFuture
(new CompletableFuture()).thenCompose((1)->{}) .thenCompose((2)->{}) .thenCompose((3)->{}) .thenCompose((4)->{}) .join()
Reactive Streams
Reactive Streams是一个倡议,它提倡提供一种带有非阻塞背压的异步流处理的标准(Reactive Streams is an initiative to provide a standard for asynchronous stream processing with non-blocking back pressure)。
JDK 9中的java.util.concurrent.Flow中的概念,与Reactive Streams是一对一对等的。java.util.concurrent.Flow是Reactive Streams标准的实现之一。
多CPU的并行机制让处理海量数据的速度更快,消息传递和响应式编程让有限的并行运行的线程执行更多的I/O操作。
HttpClient的实现细节
请求响应的body与reactive streams
- 请求响应的body暴露为reactive streams
- http client是请求的body的消费者
- http client是响应的body的生产者
HttpRequest内部
public abstract class HttpRequest { ... public interface BodyPublisher extends Flow.Publisher<ByteBuffer> { ... } }
HttpResponse的内部
public abstract class HttpResponse<T> { ... public interface BodyHandler<T> { BodySubscriber<T> apply(int statusCode, HttpHeaders responseHeaders); } public interface BodySubscriber<T> extends Flow.Subscriber<List<ByteBuffer>> { ... } }
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
从时间碎片角度理解阻塞IO模型及非阻塞模型
阻塞模型限制了服务器的并发处理能力(伸缩性或同时处理的客户端连接数) 传统的网络服务器只支持阻塞模型,该模型下,针对每个客户端连接,服务器都必须创建一个线程来处理这个连接上的请求,服务器必须维持着这些线程直到线程中的处理工作结束。 服务器上所能创建的线程数量是有限的,WHY? 进程上下文切换是耗时的过程 创建的进程本身占用资源,比如每个进程或线程占用一定容量的内存 等待数据准备和内核缓存复制,导致IO阻塞,占用着线程 所以当连接到服务器上的客户端的数量很大时,把服务器上所能创建的线程都占据了时,服务器就无法接受更多的连接了。这限制了服务器处理请求的伸缩性。 并非所有客户端都是持续活跃的 存在这样一个事实,就是虽然连接到服务器上的客户端很多,但并非所有客户端都是持续活跃着的。它们占据着阻塞式服务器的线程资源——即使它们处于非工作状态。这些线程占据了资源,却不工作。 这会造成什么现象呢?就是线程时间的碎片化——一个线程大部分时间是在等待IO操作的结果。 为了让服务器能接受更多客户端的连接,非阻塞模型就出现了。 如何提升服务器的并发处理能力? 消灭碎片化时间,可以提升服务器的并发处理能力。 ...
- 下一篇
线性表-顺序结构
应昨天,今天学习线性表的顺序结构 什么是线性表:线性表是一种典型的线性结构,是由n个元素组成的有限序列,比如字母表,点名册 对于一个非空的线性表,逻辑结构特征如下 有且仅有一个开始节点a1,没有直接前趋节点,有且仅有一个直接后继节点a2 有且仅有一个结束节点an,没有直接后继节点,有且仅有一个直接前趋节点a(n-1) 其余节点均都有一个前趋节点和一个后继节点 数据元素的类型都必须相同 线性表-顺序表的结构 顺序表就是按照顺序存储方式存储的线性表,该线性表的节点按照逻辑次序依次存放在计算机的一组连续的存储单元中(Array):java中实现为ArrayList 下面实现,下标依旧是从0开始的,下面的实现并没有加入额外的非法输入控制,只是实现了一个大概的逻辑 public class MyArrayList<T> { //默认大小 private static final int DEFAULT_SIZE = 20; //存放元素的数组对象 private Object[] elements ; private int length; public MyArrayList() ...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS8编译安装MySQL8.0.19
- SpringBoot2全家桶,快速入门学习开发网站教程
- Hadoop3单机部署,实现最简伪集群
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS7,CentOS8安装Elasticsearch6.8.6