内容简介:谈谈 Tomcat 请求处理流程
建议结合《 谈谈 Tomcat 架构及启动过程[含部署] 》一起看!
很多东西在时序图中体现的已经非常清楚了,没有必要再一步一步的作介绍,所以本文以图为主,然后对部分内容加以简单解释。
- 绘制图形使用的 工具 是 PlantUML + Visual Studio Code + PlantUML Extension
本文对 Tomcat 的介绍以 Tomcat-9.0.0.M22
为标准。
Tomcat-9.0.0.M22
是 Tomcat 目前最新的版本,但尚未发布,它实现了 Servlet4.0
及 JSP2.3
并提供了很多新特性,需要 1.8 及以上的 JDK 支持等等,详情请查阅 Tomcat-9.0-doc
Overview
- Connector 启动以后会启动一组线程用于不同阶段的请求处理过程。
-
Acceptor
线程组。用于接受新连接,并将新连接封装一下,选择一个Poller
将新连接添加到Poller
的事件队列中。 -
Poller
线程组。用于监听 Socket 事件,当 Socket 可读或可写等等时,将 Socket 封装一下添加到worker
线程池的任务队列中。 -
worker
线程组。用于对请求进行处理,包括分析请求报文并创建 Request 对象,调用容器的 pipeline 进行处理。
-
Acceptor
、Poller
、worker
所在的ThreadPoolExecutor
都维护在NioEndpoint
中。
Connector Init and Start
-
initServerSocket()
,通过ServerSocketChannel.open()
打开一个 ServerSocket,默认绑定到 8080 端口,默认的连接等待队列长度是 100, 当超过 100 个时会拒绝服务。我们可以通过配置conf/server.xml
中Connector
的acceptCount
属性对其进行定制。 -
createExecutor()
用于创建Worker
线程池。默认会启动 10 个Worker
线程,Tomcat 处理请求过程中,Woker 最多不超过 200 个。我们可以通过配置conf/server.xml
中Connector
的minSpareThreads
和maxThreads
对这两个属性进行定制。 -
Pollor
用于检测已就绪的 Socket。 默认最多不超过 2 个,Math.min(2,Runtime.getRuntime().availableProcessors());
。我们可以通过配置pollerThreadCount
来定制。 -
Acceptor
用于接受新连接。默认是 1 个。我们可以通过配置acceptorThreadCount
对其进行定制。
Requtst Process
Acceptor
-
Acceptor
在启动后会阻塞在ServerSocketChannel.accept();
方法处,当有新连接到达时,该方法返回一个SocketChannel
。 - 配置完 Socket 以后将 Socket 封装到
NioChannel
中,并注册到Poller
,值的一提的是,我们一开始就启动了多个Poller
线程,注册的时候,连接是公平的分配到每个Poller
的。NioEndpoint
维护了一个Poller
数组,当一个连接分配给pollers[index]
时,下一个连接就会分配给pollers[(index+1)%pollers.length]
. -
addEvent()
方法会将 Socket 添加到该Poller
的PollerEvent
队列中。到此Acceptor
的任务就完成了。
Poller
-
selector.select(1000)
。当Poller
启动后因为 selector 中并没有已注册的Channel
,所以当执行到该方法时只能阻塞。所有的Poller
共用一个 Selector,其实现类是sun.nio.ch.EPollSelectorImpl
-
events()
方法会将通过addEvent()
方法添加到事件队列中的 Socket 注册到EPollSelectorImpl
,当 Socket 可读时,Poller
才对其进行处理 -
createSocketProcessor()
方法将 Socket 封装到SocketProcessor
中,SocketProcessor
实现了Runnable
接口。worker
线程通过调用其run()
方法来对 Socket 进行处理。 -
execute(SocketProcessor)
方法将SocketProcessor
提交到线程池,放入线程池的workQueue
中。workQueue
是BlockingQueue
的实例。到此Poller
的任务就完成了。
Worker
-
worker
线程被创建以后就执行ThreadPoolExecutor
的runWorker()
方法,试图从workQueue
中取待处理任务,但是一开始workQueue
是空的,所以worker
线程会阻塞在workQueue.take()
方法。 - 当新任务添加到
workQueue
后,workQueue.take()
方法会返回一个Runnable
,通常是SocketProcessor
,然后worker
线程调用SocketProcessor
的run()
方法对 Socket 进行处理。 -
createProcessor()
会创建一个Http11Processor
, 它用来解析 Socket,将 Socket 中的内容封装到Request
中。注意这个Request
是临时使用的一个类,它的全类名是org.apache.coyote.Request
, -
postParseRequest()
方法封装一下 Request,并处理一下映射关系(从 URL 映射到相应的Host
、Context
、Wrapper
)。
-
CoyoteAdapter
将 Rquest 提交给Container
处理之前,并将org.apache.coyote.Request
封装到org.apache.catalina.connector.Request
,传递给Container
处理的 Request 是org.apache.catalina.connector.Request
。 -
connector.getService().getMapper().map()
,用来在Mapper
中查询 URL 的映射关系。映射关系会保留到org.apache.catalina.connector.Request
中,Container
处理阶段request.getHost()
是使用的就是这个阶段查询到的映射主机,以此类推request.getContext()
、request.getWrapper()
都是。
-
connector.getService().getContainer().getPipeline().getFirst().invoke()
会将请求传递到Container
处理,当然了Container
处理也是在Worker
线程中执行的,但是这是一个相对独立的模块,所以单独分出来一节。
Container
- 需要注意的是,基本上每一个容器的
StandardPipeline
上都会有多个已注册的Valve
,我们只关注每个容器的 Basic Valve。其他 Valve 都是在 Basic Valve 前执行。 -
request.getHost().getPipeline().getFirst().invoke()
先获取对应的StandardHost
,并执行其 pipeline。 -
request.getContext().getPipeline().getFirst().invoke()
先获取对应的StandardContext
,并执行其 pipeline。 -
request.getWrapper().getPipeline().getFirst().invoke()
先获取对应的StandardWrapper
,并执行其 pipeline。 - 最值得说的就是
StandardWrapper
的 Basic Valve,StandardWrapperValve
-
allocate()
用来加载并初始化Servlet
,值的一提的是 Servlet 并不都是单例的,当 Servlet 实现了SingleThreadModel
接口后,StandardWrapper
会维护一组 Servlet 实例,这是享元模式。当然了SingleThreadModel
在 Servlet 2.4 以后就弃用了。 -
createFilterChain()
方法会从StandardContext
中获取到所有的过滤器,然后将匹配 Request URL 的所有过滤器挑选出来添加到filterChain
中。 -
doFilter()
执行过滤链,当所有的过滤器都执行完毕后调用 Servlet 的service()
方法。
Reference
- 《How Tomcat works》
- 《Tomcat 架构解析》– 刘光瑞
- Tomcat-9.0-doc
- apache-tomcat-9.0.0.M22-src
- tomcat架构分析 (connector NIO 实现)
以上所述就是小编给大家介绍的《谈谈 Tomcat 请求处理流程》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 谈谈HTTP的请求和响应
- 谈谈axios中Post请求变成OPTIONS的几种解决方案
- 谈谈 iOS 网络层设计(SSJNetWork封装缓冲,log日志,自动网络请求)
- ????谈谈单元测试
- 谈谈日志的最佳实践
- 谈谈 Redux
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Effective Ruby:改善Ruby程序的48条建议
Peter J. Jones / 杨政权、秦五一、孟樊超 / 机械工业出版社 / 2016-1 / 49
如果你是经验丰富的Rub程序员,本书能帮助你发挥Ruby的全部力量来编写更稳健、高效、可维护和易执行的代码。Peter J.Jones凭借其近十年的Ruby开发经验,总结出48条Ruby的最佳实践、专家建议和捷径,并辅以可执行的代码实例。 Jones在Ruby开发的每个主要领域都给出了实用的建议,从模块、内存到元编程。他对鲜为人知的Ruby方言、怪癖、误区和强力影响代码行为与性能的复杂性的揭......一起来看看 《Effective Ruby:改善Ruby程序的48条建议》 这本书的介绍吧!