内容简介:本系列为本人Java编程方法论 响应式解读系列的Rxjava源码解读与分享:Reactor源码解读与分享:
本系列为本人 Java 编程方法论 响应式解读系列的 Webflux
部分,现分享出来,前置知识Rxjava2 ,Reactor的相关解读已经录制分享视频,并发布在b站,地址如下:
Rxjava源码解读与分享: www.bilibili.com/video/av345…
Reactor源码解读与分享: www.bilibili.com/video/av353…
NIO源码解读相关视频分享: www.bilibili.com/video/av432…
NIO源码解读视频相关配套文章:
BIO到NIO源码的一些事儿之NIO 下 之 Selector BIO到NIO源码的一些事儿之NIO 下 Buffer解读 上 BIO到NIO源码的一些事儿之NIO 下 Buffer解读 下
其中,Rxjava与Reactor作为本人书中内容将不对外开放,大家感兴趣可以花点时间来观看视频,本人对着两个库进行了全面彻底细致的解读,包括其中的设计理念和相关的方法论,也希望大家可以留言纠正我其中的错误。
为什么需要Spring WebFlux
在我们要面临越来越高的并发处理,传统的 Spring Web MVC
已经无法满足我们的需求,即我们需要一个无阻塞的且通过很少的硬件资源(体现就是通过很少数量的线程)的 web
框架来处理并发任务。 Servlet 3.1
确实为非阻塞I/O提供了相应API。但是,使用它时,Servlet 其余部分API的在执行时就是同步(比如 Filter
)或阻塞( getParameter
, getPart
)。 我们知道, Tomcat
这类服务器其有一个 Servlet Worker
线程池,而使用 Spring Web MVC
的话,对于请求的处理过程将会在 DispatcherServlet
中进行,而其内部默认并不会进行异步处理,所以,当有 I/O
或者耗时操作的时候,很可能会阻塞当前 Servlet
所在线程。(参考网上关于 SpringMVC
异步操作的相关博文),关于其异步改造,本人也在 RxJava2
的相关分享视频的项目实例中进行有改造,大家可回顾。而我们的目的就是将当前 Servlet
所在线程给让出来,这样可以接收更多的请求。 那两者的区别到底在什么地方, Spring WebFlux
到底有何意义可供我们迁移学习。相信大家在接触过 Tomcat
之后,都会去学习一下 Tomcat
的配置文件 server.xml
,从中我们也知道 Connector
,其主要功能是:接收连接请求,创建 Request
和 Response
对象用于和请求端交换数据;然后分配线程让 Servlet
容器来处理这个请求,并把产生的 Request
和 Response
对象传给 Servlet
。当 Servlet
处理完请求后,也会通过 Connector
将响应返回给客户端。 所以我们从 Connector
入手,讨论一些与 Connector
有关问题,包括 NIO/BIO
模式、线程池、连接数等。 根据协议的不同, Connector
可以分为 HTTP
Connector
、 AJP Connector
等,此处只讨论 HTTP Connector
。
Tomcat下Connector中的协议
Connector
在处理HTTP请求时,会使用不同的 protocol
。不同的 Tomcat版本
支持的 protocol
不同,其中典型的 protocol
包括 BIO、NIO和APR
( Tomcat7
中支持这 3
种, Tomcat8
增加了对 NIO2
的支持,而在 Tomcat8.5
和 Tomcat9.0
,则去掉了对 BIO
的支持)。
Connector
使用哪种 protocol
,可以通过 Tomcat
配置文件 server.xml
中的 <connector>
元素中的 protocol
属性进行指定,也可以使用默认值。如果没有指定 protocol
,则使用默认值 HTTP/1.1
,其含义如下:在 Tomcat7
中,自动选取使用 BIO
或 APR
(如果找到 APR
需要的本地库,则使用 APR
,否则使用 BIO
);在 Tomcat8
中,自动选取使用 NIO
或 APR
(如果找到 APR
需要的本地库,则使用 APR
,否则使用 NIO
)。
无论是 BIO
,还是 NIO
, Connector
处理请求的大致流程是一样的: 在accept队列中接收连接(当客户端向服务器发送请求时,如果客户端与服务端完成三次握手建立了连接,则服务端将该连接放入accept队列);在连接中获取请求的数据,生成request;调用Servlet容器处理请求;返回response。
为了便于大家的理解,这里先明确一下连接与请求的关系:
- 连接是
TCP
层面的(传输层),对应socket
。 - 请求是
HTTP
层面的(应用层),必须依赖于TCP
的连接实现。 - 一个
TCP
连接中可能传输多个HTTP
请求。
BIO是Blocking IO,顾名思义是阻塞的IO;NIO是Non-blocking IO,则是非阻塞的IO。而APR是Apache Portable Runtime,是Apache可移植运行库,利用本地库可以实现高可扩展性、高性能;Apr是在Tomcat上运行高并发应用的首选模式,但是需要安装apr、apr-utils、tomcat-native等包。
在 BIO
实现的 Connector
中,请求主要是由 JioEndpoint
对象来处理。 JioEndpoint
维护了 Acceptor
和 Worker
,通过 Acceptor
接收 socket
,然后从 Worker线程池
中找出空闲的线程处理 socket
,如果 worker线程池
没有空闲线程,则 Acceptor
将阻塞。其中 Worker
是 Tomcat
自带的线程池,如果通过 <Executor>
配置了其他线程池,原理与 Worker
类似。
在 NIO
实现的 Connector
中,处理请求的主要实体是 NIOEndpoint
对象。 NIOEndpoint
中除了包含 Acceptor
和 Worker
外,还是用了 Poller
,处理流程如下图所示:
图中 Acceptor
及 Worker
分别是以线程池形式存在, Poller
是一个单线程(此处是其与Netty最大的区别)。注意,与 BIO
的实现一样,这里,需要提及的是,在 server.xml
中没有配置 <Executor>
,则以 Worker线程池
运行,如果配置了 <Executor>
,则以基于 java.util.concurrent.ThreadPoolExecutor
线程池运行。
由图可知, Acceptor
接收 socket
后(这里虽然是基于 NIO
的 connector
,但是在接收 socket
方面还是传统的 serverSocket.accept()
方式,获得 SocketChannel
对象,然后封装在一个 tomcat
的 org.apache.tomcat.util.net.NIOChannel
实现类对象,并将之包装为一个 PollerEvent对象
),并不是直接使用 Worker
中的线程处理请求,而是先将 PollerEvent对象
发送给了 Poller
,而 Poller
是实现 NIO
的关键。 Acceptor
向 Poller
发送 包装后的请求
通过添加队列的操作实现,这里使用了典型的生产者-消费者模式。同时,在 Poller
中,维护了一个 Selector
对象;当 Poller
从队列中取出 socket
后,注册到该 Selector
中;然后通过遍历 Selector
,找出其中可读的 socket
,并使用 Worker
中的线程处理相应请求。与 BIO
类似, Worker
也可以被自定义的线程池代替。
通过上述过程可以看出,在 NIOEndpoint
处理请求的过程中,无论是 Acceptor
接收 socket
,还是线程处理请求(添加到 Poller
队列是同步的),使用的仍然是阻塞方式;但在 读取socket并交给Worker中的线程
的这个过程中,使用非阻塞的 NIO
实现,这是 NIO
模式与 BIO
模式的最主要区别(其他区别对性能影响较小)。而也是由于这个区别,在并发量较大的情形下可以给Tomcat效率带来显著提升。
目前大多数 HTTP
请求使用的是长连接( HTTP/1.1
默认 keep-alive
为 true
),而长连接意味着,一个 TCP
的 socket
在当前请求结束后,如果没有新的请求到来, socket
不会立马释放,而是等 timeout
后再释放。如果使用 BIO
, 读取socket并交给Worker中的线程
这个过程是阻塞的,也就意味着在 socket
等待下一个请求或等待释放的过程中,处理这个 socket
的工作线程会一直被占用,无法释放;因此Tomcat可以同时处理的socket数目不能超过最大线程数,性能受到了极大限制。而使用NIO, 读取socket并交给Worker中的线程
这个过程是非阻塞的(是由 Poller
所在线程维护的),并不会占用工作线程,因此Tomcat可以同时处理的 socket
数目不受最大线程数约束,并发性能也就大大提高,但 Poller
同时也是其性能瓶颈。
因此,随着 NIO
所实现 Connector
的引入,客户端到服务器的通信是非阻塞的,但是服务器到 servlet
的连接仍然是阻塞的,也就意味着每个请求都会阻塞一个线程,也就导致我们会看到一个线程处理一个请求的模型。 因此,随着 Servlet
容器的发展, Servlet API
也就需要非阻塞支持,也就是 Servlet 3.1+
。
关于 Tomcat
下 Connector
的更多深入解读,有感兴趣的可以参考本人的另一篇博文 tomcat从启动到接轨Servlet二三事
以上所述就是小编给大家介绍的《Java编程方法论-Spring WebFlux篇 01 为什么需要Spring WebFlux 上》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Java编程方法论-Spring WebFlux篇 01 为什么需要Spring WebFlux 下
- Java编程方法论-Spring WebFlux篇 Reactor-Netty下HttpServer 的封装
- Java编程方法论-Spring WebFlux篇 Reactor-Netty下TcpServer的功能实现 1
- 架构设计方法论
- 性能分析方法论
- 架构制图:工具与方法论
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Design systems
Not all design systems are equally effective. Some can generate coherent user experiences, others produce confusing patchwork designs. Some inspire teams to contribute to them, others are neglected. S......一起来看看 《Design systems》 这本书的介绍吧!