原文出处:
Rainstorm
建议结合《谈谈 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()
会创建一个Http1