6 timing diagrams! Talk about Tomcat request processing flow

Many things are already very clear in the sequence diagrams, there is no need to introduce them step by step. This article focuses on diagrams, and then briefly explains some of the content.

  • The tool used for drawing graphics is PlantUML + Visual Studio Code + PlantUML Extension

The introduction of Tomcat in this article is based on Tomcat-9.0.0.M22.

Tomcat-9.0.0.M22 is the latest version of Tomcat, but it has not yet been released. It implements Servlet4.0 and JSP2.3 and provides many new features. It requires JDK 1.8 and above to support, etc. For details, please refer to Tomcat- 9.0-doc

  • Overview
  • Connector Init and Start
  • Requtst Process
  • Acceptor
  • Poller
  • Worker
  • Container
  • At last

Overview

Insert picture description here

  1. After the Connector is started, a set of threads will be started for different stages of the request processing process.
  2. Acceptor thread group. Used to accept new connections, encapsulate the new connections, select a Poller to add the new connection to Poller's event queue.
  3. Poller thread group. Used to monitor Socket events. When the Socket is readable or writable, etc., encapsulate the Socket and add it to the task queue of the worker thread pool.
  4. worker thread group. Used to process the request, including analyzing the request message and creating a Request object, and calling the container's pipeline for processing.
  • The ThreadPoolExecutor where Acceptor, Poller, and worker are located are all maintained in NioEndpoint.

Connector Init and Start

Insert picture description here

  1. initServerSocket(), open a ServerSocket through ServerSocketChannel.open(), which is bound to port 8080 by default. The default connection waiting queue length is 100. When it exceeds 100, the service will be rejected. We can customize it by configuring the acceptCount property of Connector in conf/server.xml.
  2. createExecutor() is used to create the Worker thread pool. By default, 10 Worker threads are started. When Tomcat is processing the request, Woker does not exceed 200 at most. We can customize these two attributes by configuring the minSpareThreads and maxThreads of the Connector in conf/server.xml.
  3. Pollor is used to detect the ready socket. The default is no more than 2, Math.min(2,Runtime.getRuntime().availableProcessors());. We can customize it by configuring pollerThreadCount.
  4. Acceptor is used to accept new connections. The default is 1. We can customize it by configuring acceptorThreadCount.

Requtst Process

Acceptor
Insert picture description here

  1. Acceptor will block at the ServerSocketChannel.accept(); method after it is started. When a new connection arrives, the method returns a SocketChannel.
  2. After configuring the Socket, encapsulate the Socket into the NioChannel and register it with Poller. The value is that we started multiple Poller threads at the beginning. When registering, the connection is fairly distributed to each Poller. NioEndpoint maintains a Poller array. When a connection is allocated to pollers[index], the next connection will be allocated to pollers[(index+1)%pollers.length].
  3. The addEvent() method will add the Socket to the PollerEvent queue of this Poller. At this point, the Acceptor's task is completed.

Poller

Insert picture description here

  1. selector.select(1000). When Poller is started, because there is no registered Channel in the selector, it can only block when the method is executed. All Pollers share a Selector, and its implementation class is sun.nio.ch.EPollSelectorImpl
  2. The events() method will register the Socket added to the event queue through the addEvent() method to EPollSelectorImpl, and Poller will process it only when the Socket is readable
  3. The createSocketProcessor() method encapsulates the Socket into the SocketProcessor, which implements the Runnable interface. The worker thread processes the Socket by calling its run() method.
  4. The execute(SocketProcessor) method submits the SocketProcessor to the thread pool and puts it into the workQueue of the thread pool. workQueue is an instance of BlockingQueue. At this point, Poller's task is complete.

Worker

Insert picture description here

  1. After the worker thread is created, it executes the runWorker() method of ThreadPoolExecutor, trying to fetch the pending tasks from the workQueue, but the workQueue is empty at first, so the worker thread will be blocked in the workQueue.take() method.
  2. When a new task is added to the workQueue, the workQueue.take() method returns a Runnable, usually a SocketProcessor, and then the worker thread calls the run() method of the SocketProcessor to process the Socket.
  3. createProcessor() will create an Http11Processor, which is used to parse the Socket and encapsulate the contents of the Socket into the Request. Note that this Request is a temporarily used class, its full class name is org.apache.coyote.Request,
  4. The postParseRequest() method encapsulates the Request and handles the mapping relationship (from the URL to the corresponding Host, Context, and Wrapper).
  5. CoyoteAdapter encapsulates the org.apache.coyote.Request to org.apache.catalina.connector.Request before submitting the Rquest to the Container for processing. The Request passed to the Container for processing is org.apache.catalina.connector.Request.
  6. connector.getService().getMapper().map(), used to query the mapping relationship of URLs in Mapper. The mapping relationship will be retained in org.apache.catalina.connector.Request, the container processing stage request.getHost() uses the mapping host queried at this stage, and so on request.getContext(), request.getWrapper() Both.
  7. connector.getService().getContainer().getPipeline().getFirst().invoke() will pass the request to the Container for processing. Of course, the Container processing is also executed in the Worker thread, but this is a relatively independent module. So separate a section separately.

Container

Insert picture description here

  1. It should be noted that basically there will be multiple registered valves on the StandardPipeline of each container, and we only focus on the Basic Valve of each container. All other valves are executed before the basic valve.
  2. request.getHost().getPipeline().getFirst().invoke() First obtain the corresponding StandardHost and execute its pipeline.
  3. request.getContext().getPipeline().getFirst().invoke() First obtain the corresponding StandardContext and execute its pipeline.
  4. request.getWrapper().getPipeline().getFirst().invoke() First obtain the corresponding StandardWrapper and execute its pipeline.
  5. The most worth mentioning is StandardWrapper's Basic Valve, StandardWrapperValve
  6. allocate() is used to load and initialize Servlet. The value of Servlet is that not all Servlets are singletons. When Servlet implements the SingleThreadModel interface, StandardWrapper will maintain a set of Servlet instances. This is the flyweight model. Of course, SingleThreadModel is deprecated after Servlet 2.4.
  7. The createFilterChain() method will get all the filters from the StandardContext, and then select all the filters that match the Request URL and add them to the filterChain.
  8. doFilter() executes the filter chain, and calls the service() method of the Servlet when all filters are executed.

Reader benefits

Thank you for seeing here!
I have compiled a lot of 2021 latest Java interview questions (including answers) and Java study notes here, as shown below
Insert picture description here

The answers to the above interview questions are organized into document notes. As well as interviews also compiled some information on some of the manufacturers & interview Zhenti latest 2021 collection (both documenting a small portion of the screenshot) free for everyone to share, in need can click to enter signal: CSDN! Free to share~

If you like this article, please forward it and like it.

Remember to follow me!

Guess you like

Origin blog.csdn.net/weixin_49527334/article/details/113652551