当前位置: 首页 > 科技观察

说说Tomcat的请求处理流程

时间:2023-03-22 11:03:55 科技观察

很多东西在时序图中已经体现的很清楚了,没必要再一步步介绍了,所以本文主要以图为主,然后对其中的一些内容进行简单说明.用来绘制图形的工具是PlantUML+VisualStudioCode+PlantUMLExtension。本文对Tomcat的介绍以Tomcat-9.0.0.M22为标准。Tomcat-9.0.0.M22是Tomcat的最新版本,但是还没有发布。它实现了Servlet4.0和JSP2.3,并提供了许多新特性。需要1.8以上的JDK支持等,具体请参考Tomcat-9.0-doc。https://tomcat.apache.org/tomcat-9.0-doc/index.htmlOverviewConnector启动后会启动一组线程,用于不同阶段的请求处理。接受者线程组。用于接受新连接,封装新连接,选择一个Poller并将新连接添加到Poller的事件队列中。轮询线程组。用于监听Socket事件,当Socket可读或可写等时,将Socket封装起来加入到工作线程池的任务队列中。工作线程组。用于处理请求,包括解析请求消息并创建Request对象,调用容器的pipeline进行处理。Acceptor、Poller、Worker所在的ThreadPoolExecutor都维护在NioEndpoint中。ConnectorInit并启动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用于检测就绪的Sockets。默认不超过2,Math.min(2,Runtime.getRuntime().availableProcessors());。我们可以通过配置pollerThreadCount来自定义。Acceptor用于接受新的连接。默认为1,我们可以通过配置acceptorThreadCount来自定义。RequestProcessAcceptorAcceptor会阻塞在ServerSocketChannel.accept();启动后的方法。当新连接到达时,此方法返回一个SocketChannel。配置好Socket后,将Socket封装成一个NioChannel,注册到Poller中。值得一提的是,我们一开始就开启了多个Poller线程。注册时,连接被公平地分配给每个Poller。NioEndpoint维护着一个Poller数组。当一个连接分配给pollers[index]时,下一个连接将分配给pollers[(index+1)%pollers.length]。addEvent()方法会将Socket添加到Poller的PollerEvent队列中。至此,Acceptor的任务就完成了。轮询选择器。选择(1000)。当Poller启动时,由于selector中没有注册的Channel,所以只能在这个方法执行的时候阻塞。所有的Poller共享一个Selector,其实现类为sun.nio.ch.EPollSelectorImpl。events()方法会将通过addEvent()方法添加到事件队列中的Socket注册到EPollSelectorImpl中。当Socket可读时,Poller将选择它。处理createSocketProcessor()方法将Socket封装成SocketProcessor,SocketProcessor实现了Runnable接口。工作线程通过调用其run()方法来处理Socket。execute(SocketProcessor)方法将SocketProcessor提交给线程池,放入线程池的workQueue中。workQueue是BlockingQueue的一个实例。至此Poller的任务就完成了。Worker工作线程创建后,会执行ThreadPoolExecutor的runWorker()方法,试图从workQueue中获取待处理的任务,但是一开始workQueue是空的,所以工作线程会阻塞在workQueue.take()中方法。当workQueue中加入新任务时,workQueue.take()方法会返回一个Runnable,通常是一个SocketProcessor,然后工作线程调用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中会保留映射关系,容器处理阶段request.getHost()使用该阶段查询到的映射主机,等等request.getContext(),request.getWrapper()两者都是。connector.getService().getContainer().getPipeline().getFirst().invoke()会将请求传递给Container进行处理。当然Container的处理也是在Worker线程中执行的,但是这是一个相对独立的模块。所以单独出来一个单独的部分。需要注意的是,对于Container来说,每个容器的StandardPipeline上基本上都有多个注册的Valve,我们只关注每个容器的BasicValve。其他Valves在BasicValve之前执行。request.getHost().getPipeline().getFirst().invoke()先获取对应的StandardHost,执行其pipeline。request.getContext().getPipeline().getFirst().invoke()先获取对应的StandardContext,然后执行其pipeline。request.getWrapper().getPipeline().getFirst().invoke()先获取对应的StandardWrapper,然后执行其pipeline。最值得一提的是StandardWrapper的BasicValve。StandardWrapperValveallocate()用于加载和初始化Servlet。值得一提的是,并不是所有的Servlet都是单例的。当一个Servlet实现了SingleThreadModel接口时,StandardWrapper会维护一组Servlets例如,这就是Flyweight模式。当然,SingleThreadModel在Servlet2.4之后就被弃用了。createFilterChain()方法会从StandardContext中获取所有的过滤器,然后挑选出所有匹配RequestURL的过滤器并将它们添加到filterChain中。doFilter()执行过滤器链,并在所有过滤器执行完毕后调用Servlet的service()方法。参考《How Tomcat works》https://www.amazon.com/How-Tomcat-Works-Budi-Kurniawan/dp/097521280X《Tomcat 架构解析》–刘光瑞http://product.dangdang.com/25084132.htmlTomcat-9.0-文档https://tomcat.apache.org/tomcat-9.0-doc/index.htmlapache-tomcat-9.0.0.M22-srchttp://www-eu.apache.org/dist/tomcat/tomcat-9/v9.0.0.M22/src/tomcat架构分析(connectorNIO实现)http://gearever.iteye.com/blog/1844203