Tomcat 请求处理流程详解
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.EPollSelectorImplevents()方法会将通过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()方法。
- 点赞
- 收藏
- 关注作者






评论(0)