6张图说清楚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 Init and Start
Requtst Process
Acceptor
Poller
Worker
Container
At last
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 实现)
最后
欢迎大家一起交流,喜欢文章记得点个赞哟,感谢支持!
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
助力共享经济,芝麻信用背后的技术| 9月10号栖夜读
点击订阅云栖夜读日刊,专业的技术干货,不容错过! 阿里专家原创好文 1.助力共享经济,芝麻信用背后的技术 近期,CCTV9播放了自制的系列纪录片《大数据时代》,该片是国内首部大数据产业题材纪录片,节目细致而生动地讲述了大数据技术在政府治理、民生服务、数据安全、工业转型、未来生活等方面给我们带来的改变和影响。在第四集中,讲述了芝麻信用如何助力共享经济,推动商业信用体系建设的故事。我们将其摘录分享出来,并简要介绍芝麻信用背后的技术。阅读更多》》 2.读透《阿里巴巴数据中台实践》,其到底有什么高明之处? 最近阿里巴巴分享了《阿里巴巴数据中台实践》这个PPT(自行搜索原始文章),对于数据中台的始作俑者,还是要怀着巨大的敬意去学习的,因此仔细的研读了,希望能发现一些不一样的东西。读这些专业的PPT,实际是非常耗时的,你需要把这些PPT外表的光鲜扒光,死抠上面的每一个字去理解底下隐藏的含义,然后跟你的已有知识体系去对比,看看是否有助于完善自己的认知,对于自己不理解的,还需要经常去检索相关的文档。当然,很多写PPT的用词没这么严谨,临时造概念的不少,或者是独特的说法,因此有时候还要做一些揣测,结合自...
- 下一篇
Java程序员面试中最容易答错的8道面试题,你中坑了吗?
1. static 和 final 的用法 static 的作用从三个方面来谈,分别是静态变量、静态方法、静态类。 静态变量:声明为 static 的静态变量实质上就是全局变量,当声明一个对象时,并不产生static 变量的拷贝,而是该类所有实例变量共用同一个 static 变量。也就是说这个静态变量只加载一次,只分配一块储存空间。 静态方法: 声明为static的静态方法有以下几个特点: (1)静态方法只能调用静态方法; (2)静态方法只能访问静态数据; (3)静态方法不能以任何方式引用this或super; 静态类:通常一个普通类不允许声明为静态,只有一个内部类才可以(main方法就是一个典型),这时这个声明的静态类可以直接作为一个普通类来使用,而不需要实例一个外部类。 final 的作用从变量、方法、类三个方面来理解: final修饰的变量的值不能被修改,是一个常量; final修饰的方法不能被重写; final修饰的类不能被继承; 2. 抽象类和接口的区别,类可以继承多个类吗,接口可以继承多个接口吗,类可以实现多个接口吗? 抽象类和接口都不能直接实例化,如果要实例化,抽象类变量...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2全家桶,快速入门学习开发网站教程
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS关闭SELinux安全模块
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- CentOS7设置SWAP分区,小内存服务器的救世主
- SpringBoot2整合Thymeleaf,官方推荐html解决方案