谈谈 Tomcat 架构及启动过程[含部署]

栏目: Java · 发布时间: 6年前

内容简介:谈谈 Tomcat 架构及启动过程[含部署]

这个题目命的其实是很大的,写的时候还是很忐忑的,但我尽可能把这个过程描述清楚。因为这是读过源码以后写的总结,在写的过程中可能会忽略一些前提条件,如果有哪些比较突兀就出现,或不好理解的地方可以给我提 Issue,我会尽快补充修订相关内容。

很多东西在时序图中体现的已经非常清楚了,没有必要再一步一步的作介绍,所以本文以图为主,然后对部分内容加以简单解释。

本文对 Tomcat 的介绍以 Tomcat-9.0.0.M22 为标准。

Tomcat-9.0.0.M22 是 Tomcat 目前最新的版本,但尚未发布,它实现了  Servlet4.0 及  JSP2.3 并提供了很多新特性,需要 1.8 及以上的 JDK 支持等等,详情请查阅  Tomcat-9.0-doc

Overview

谈谈 Tomcat 架构及启动过程[含部署]

  1. Bootstrap  作为 Tomcat 对外界的启动类,在  $CATALINA_BASE/bin  目录下,它通过反射创建  Catalina  的实例并对其进行初始化及启动。
  2. Catalina  解析  $CATALINA_BASE/conf/server.xml  文件并创建  StandardServerStandardServiceStandardEngineStandardHost  等
  3. StandardServer  代表的是整个 Servlet 容器,他包含一个或多个  StandardService
  4. StandardService  包含一个或多个  Connector ,和一个  EngineConnector  和  Engine  都是在解析  conf/server.xml  文件时创建的, Engine  在 Tomcat 的标准实现是  StandardEngine
  5. MapperListener  实现了  LifecycleListener  和  ContainerListener  接口用于监听容器事件和生命周期事件。该监听器实例监听所有的容器,包括  StandardEngineStandardHostStandardContextStandardWrapper ,当容器有变动时,注册容器到  Mapper
  6. Mapper  维护了 URL 到容器的映射关系。当请求到来时会根据  Mapper  中的映射信息决定将请求映射到哪一个  HostContextWrapper
  7. Http11NioProtocol  用于处理 HTTP/1.1 的请求
  8. NioEndpoint  是连接的端点,在请求处理流程中该类是核心类,会重点介绍。
  9. CoyoteAdapter  用于将请求从 Connctor 交给 Container 处理。使 Connctor 和 Container 解耦。
  10. StandardEngine  代表的是 Servlet 引擎,用于处理  Connector  接受的 Request。包含一个或多个  Host (虚拟主机),  Host  的标准实现是  StandardHost
  11. StandardHost  代表的是虚拟主机,用于部署该虚拟主机上的应用程序。通常包含多个  Context  (Context 在 Tomcat 中代表应用程序)。 Context  在 Tomcat 中的标准实现是  StandardContext
  12. StandardContext  代表一个独立的应用程序,通常包含多个  Wrapper ,一个  Wrapper  容器封装了一个 Servlet, Wrapper 的标准实现是  StandardWrapper
  13. StandardPipeline  组件代表一个流水线,与  Valve (阀)结合,用于处理请求。  StandardPipeline  中含有多个  Valve , 当需要处理请求时,会逐一调用  Valve  的  invoke  方法对 Request 和 Response 进行处理。特别的,其中有一个特殊的  Valve  叫  basicValve ,每一个标准容器都有一个指定的  BasicValve ,他们做的是最核心的工作。
    • StandardEngine  的是  StandardEngineValve ,他用来将 Request 映射到指定的  Host ;
    • StandardHost  的是  StandardHostValve , 他用来将 Request 映射到指定的  Context ;
    • StandardContext  的是  StandardContextValve ,它用来将 Request 映射到指定的  Wrapper
    • StandardWrapper  的是  StandardWrapperValve ,他用来加载 Rquest 所指定的 Servlet,并调用 Servlet 的  Service  方法。

Tomcat init

谈谈 Tomcat 架构及启动过程[含部署]

  • 当通过  ./startup.sh  脚本或直接通过  java  命令来启动  Bootstrap  时,Tomcat 的启动过程就正式开始了,启动的入口点就是  Bootstrap  类的  main  方法。
  • 启动的过程分为两步,分别是  init  和  start ,本节主要介绍  init ;
  • 初始化类加载器。[关于 Tomcat 类加载机制,可以参考我之前写的一片文章: 谈谈 Java 类加载机制 ]
  1. 通过从  CatalinaProperties  类中获取  common.loader  等属性,获得类加载器的扫描仓库。 CatalinaProperties  类在的静态块中调用了  loadProperties()  方法,从  conf/catalina.properties  文件中加载了属性.(即在类创建的时候属性就已经加载好了)。
  2. 通过  ClassLoaderFactory  创建  URLClassLoader  的实例
  • 通过反射创建  Catalina  的实例并设置  parentClassLoader
  • setAwait(true) 。设置  Catalina  的  await  属性为  true 。在 Start 阶段尾部,若该属性为  true ,Tomcat 会在 main 线程中监听  SHUTDOWN  命令,默认端口是 8005.当收到该命令后执行  Catalina  的  stop()  方法关闭 Tomcat 服务器。
  • createStartDigester() Catalina  的该方法用于创建一个 Digester 实例,并添加解析  conf/server.xml  的  RuleSet 。Digester 原本是 Apache 的一个开源项目,专门解析 XML 文件的,但我看 Tomcat-9.0.0.M22 中直接将这些类整合到 Tomcat 内部了,而不是引入 jar 文件。Digester 工具的原理不在本文的介绍范围,有兴趣的话可以参考  The Digester Component – Apache  或  《How Tomcat works》- Digester [推荐]  一章
  • parse()  方法就是 Digester 处理  conf/server.xml  创建各个组件的过程。值的一提的是这些组件都是使用反射的方式来创建的。特别的,在创建 Digester 的时候,添加了一些特别的  rule Set ,用于创建一些十分核心的组件,这些组件在  conf/server.xml  中没有但是其作用都比较大,这里做下简单介绍,当 Start 时用到了再详细说明:
  1. EngineConfigLifecycleListener  的实现类,触发 Engine 的生命周期事件后调用,这个监听器没有特别大的作用,就是打印一下日志
  2. HostConfigLifecycleListener  的实现类,触发 Host 的生命周期事件后调用。这个监听器的作用就是部署应用程序,这包括  conf/<Engine>/<Host>/  目录下所有的 Context xml 文件 和  webapps  目录下的应用程序,不管是 war 文件还是已解压的目录。 另外后台进程对应用程序的热部署也是由该监听器负责的。
  3. ContextConfigLifecycleListener  的实现类,触发 Context 的生命周期事件时调用。这个监听器的作用是配置应用程序,它会读取并合并  conf/web.xml  和 应用程序的  web.xml ,分析  /WEB-INF/classes/  和  /WEB-INF/lib/*.jar 中的 Class 文件的注解,将其中所有的 Servlet、ServletMapping、Filter、FilterMapping、Listener 都配置到  StandardContext  中,以备后期使用。当然了  web.xml  中还有一些其他的应用程序参数,最后都会一并配置到  StandardContext  中。
  • reconfigureStartStopExecutor()  用于重新配置启动和停止子容器的  Executor 。默认是 1 个线程。我们可以配置  conf/server.xml  中  Engine  的  startStopThreads ,来指定用于启动和停止子容器的线程数量,如果配置 0 的话会使用  Runtime.getRuntime().availableProcessors()  作为线程数,若配置为负数的话会使用  Runtime.getRuntime().availableProcessors() + 配置值 ,若和小与 1 的话,使用 1 作为线程数。当线程数是 1 时,使用  InlineExecutorService  它直接使用当前线程来执行启动停止操作,否则使用  ThreadPoolExecutor  来执行,其最大线程数为我们配置的值。
  • 需要注意的是 Host 的  init  操作是在 Start 阶段来做的,  StardardHost  创建好后其  state  属性的默认值是  LifecycleState.NEW ,所以在其调用  startInternal()  之前会进行一次初始化。

Tomcat Start[Deployment]

谈谈 Tomcat 架构及启动过程[含部署]

  • 图中从  StandardHost  Start  StandardContext  的这步其实在真正的执行流程中会直接跳过,因为  conf/server.xml  文件中并没有配置任何的  Context ,所以在  findChildren()  查找子容器时会返回空数组,所以之后遍历子容器来启动子容器的  for  循环就直接跳过了。
  • 触发 Host 的  BEFORE_START_EVENT  生命周期事件, HostConfig  调用其  beforeStart()  方法创建  $CATALINA_BASE/webapps $CATALINA_BASE/conf/<Engine>/<Host>/  目录。
  • 触发 Host 的  START_EVENT  生命周期事件, HostConfig  调用其  start()  方法开始部署已在  $CATALINA_BASE/webapps  &  $CATALINA_BASE/conf/<Engine>/<Host>/  目录下的应用程序。
  1. 解析  $CATALINA_BASE/conf/<Engine>/<Host>/  目录下所有定义  Context  的 XML 文件,并添加到  StandardHost 。这些 XML 文件称为应用程序描述符。正因为如此,我们可以配置一个虚拟路径来保存应用程序中用到的图片,详细的配置过程请参考  开发环境配置指南 – 6.3. 配置图片存放目录
  2. 部署  $CATALINA_BASE/webapps  下所有的 WAR 文件,并添加到  StandardHost
  3. 部署  $CATALINA_BASE/webapps  下所有已解压的目录,并添加到  StandardHost

特别的,添加到 StandardHost 时,会直接调用  StandardContext 的  start() 方法来启动应用程序。启动应用程序步骤请看 Context Start 一节。

  • 在  StandardEngine  和  StandardContext  启动时都会调用各自的  threadStart()  方法,该方法会创建一个新的后台线程来处理该该容器和子容器及容器内各组件的后台事件。 StandardEngine  会直接创建一个后台线程, StandardContext  默认是不创建的,和  StandardEngine  共用同一个。后台线程处理机制是周期调用组件的  backgroundProcess()  方法。详情请看 Background process 一节。
  • MapperListener
  1. addListeners(engine)  方法会将该监听器添加到  StandardEngine  和它的所有子容器中
  2. registerHost()  会注册所有的  Host  和他们的子容器到  Mapper  中,方便后期请求处理时使用。
  3. 当有新的应用( StandardContext )添加进来后,会触发 Host 的容器事件,然后通过  MapperListener  将新应用的映射注册到  Mapper  中。
  • Start 工作都做完以后  Catalina  会创建一个  CatalinaShutdownHook  并注册到 JVM。 CatalinaShutdownHook  继承了  Thread ,是  Catalina  的内部类。其  run  方法中直接调用了  Catalina  的  stop()  方法来关闭整个服务器。注册该 Thread 到 JVM 的原因是防止用户非正常终止 Tomcat,比如直接关闭命令窗口之类的。当直接关闭命令窗口时,操作系统会向 JVM 发送一个终止信号,然后 JVM 在退出前会逐一启动已注册的 ShutdownHook 来关闭相应资源。

Context Start

谈谈 Tomcat 架构及启动过程[含部署]

  • StandRoot  类实现了  WebResourceRoot  接口,它容纳了一个应用程序的所有资源,通俗的来说就是部署到  webapps  目录下对应  Context  的目录里的所有资源。因为我对 Tomcat 的资源管理部分暂时不是很感兴趣,所以资源管理相关类只是做了简单了解,并没有深入研究源代码。
  • resourceStart()  方法会对  StandardRoot  进行初始配置
  • postWorkDirectory()  用于创建对应的工作目录  $CATALINA_BASE/work/<Engine>/<Host>/<Context> , 该目录用于存放临时文件。
  • StardardContext  只是一个容器,而  ApplicationContext  则是一个应用程序真正的运行环境,相关类及操作会在请求处理流程看完以后进行补充。
  • StardardContext  触发  CONFIGURE_START_EVENT  生命周期事件, ContextConfig  开始调用  configureStart()  对应用程序进行配置。
  1. 这个过程会解析并合并  conf/web.xml  &  conf/<Engine>/<Host>/web.xml.default  &  webapps/<Context>/WEB-INF/web.xml  中的配置。
  2. 配置配置文件中的参数到  StandardContext , 其中主要的包括 Servlet、Filter、Listener。
  3. 因为从 Servlet3.0 以后是直接支持注解的,所以服务器必须能够处理加了注解的类。Tomcat 通过分析  WEB-INF/classes/  中的 Class 文件和  WEB-INF/lib/  下的 jar 包将扫描到的 Servlet、Filter、Listerner 注册到  StandardContext
  4. setConfigured(true) ,是非常关键的一个操作,它标识了 Context 的成功配置,若未设置该值为 true 的话,Context 会启动失败。

Background process

谈谈 Tomcat 架构及启动过程[含部署]

  • 后台进程的作用就是处理一下 Servlet 引擎中的周期性事件,处理周期默认是 10s。
  • 特别的  StandardHost  的  backgroundProcess()  方法会触发 Host 的  PERIODIC_EVENT  生命周期事件。然后  HostConfig  会调用其  check()  方法对已加载并进行过重新部署的应用程序进行  reload  或对新部署的应用程序进行热部署。热部署跟之前介绍的部署步骤一致,  reload()  过程只是简单的顺序调用  setPause(true)、stop()、start()、setPause(false) ,其中  setPause(true)  的作用是暂时停止接受请求。

How to read excellent open source projects

真正的第一次阅读开源项目源代码,收获还是很大的。让我在架构设计、面向对象思想、 设计模式 、Clean Code等等各个方面都有了进步。阅读优秀的开源项目其实是一件很爽的事,因为时不时的会发现一个新的设计思路,然后不由自主的感叹一声居然还可以这样!当然了,读的时候还是会有一些痛点的,比如说碰到一个变量,但是死活就是找不到初始化的位置,有时通过 Find Usage 工具可以找到,但有些找不到的只能从头开始再过一边源码。有时碰到一个设计思路死活都想不明白为什么这样设计等等,这种情况就只能通过分析更高一层的架构来解决了等等。

下面我简单分享一下我是如何阅读开源项目源码的。

  • 先找一些介绍该项目架构的书籍来看,项目架构是项目核心中的核心,读架构读的是高层次的设计思路,读源码读的是低层次的实现细节。有了高层次的设计思路做指导,源码读起来才会得心应手,因为读的时候心里很清楚现在在读的源码在整个项目架构中处于什么位置。我在读 Tomcat 源码之前先把 《How Tomcat works》 一书过了一边,然后又看了一下 《Tomcat 架构解析》 的第二章,对 Tomcat 的架构有了初步了解。(PS:《How Tomcat works》一书是全英文的,但读起来非常流畅,虽然它是基于 Tomcat 4 和 5 的,但 Tomcat 架构没有非常大的变化,新版的 Tomcat 只是增加了一些组件,如果你要学习 Tomcat 的话,首推这本书!)
  • 如果实在找不到讲架构的书,那就自己动手画类图吧!一般来说,开源项目都是为了提供服务的,我们把提供服务的流程作为主线来分析源代码,这样目的性会更强一些,将该流程中涉及到的类画到类图中,最后得到的类图就是架构!不过分析之前你要先找到流程的入口点,否则分析就无从开始。以 Tomcat 为例,他的主线流程大致可以分为 3 个:启动、部署、请求处理。他们的入口点就是  Bootstrap  类和 接受请求的  Acceptor  类!
  • 有了阅读思路我们下面来说说工具吧。我使用的阅读工具是 IntelliJ IDEA,一款十分强大的 IDE,可能比较重量级,如果你有其他更加轻量级的 Linux 平台源码阅读工具,可以推荐给我~
  1. Structure 栏目可以自定义列出类中的域、方法,然后还可以按照继承结构对域和方法进行分组,这样就可以直接看出来域和方法是在继承结构中哪个类里定义的。当你点击方法和域时,还可以自动滚动到源代码等等。
  2. 在源代码中  点击右键 -> Diagrams -> show Diagram  可以显示类的继承结构,图中包含了该类所有的祖先和所有的接口。在该图中选择指定的父类和接口, 点击右键 -> show Implementations , IDEA 会列出接口的实现类或该类的子类。
  3. FindUsage、Go To Declaration 等等就不再多说了。
  4. 目前想到的就这么多,如果你发现了我没有提到的功能,欢迎跟我邮件交流~

Reference

  1. 《How Tomcat works》
  2. 《Tomcat 架构解析》– 刘光瑞
  3. Tomcat-9.0-doc
  4. apache-tomcat-9.0.0.M22-src

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

谁说菜鸟不会数据分析

谁说菜鸟不会数据分析

张文霖、刘夏璐、狄松 编著 / 电子工业出版社 / 2011-7 / 59.00元

《谁说菜鸟不会数据分析(全彩)》内容简介:很多人看到数据分析就望而却步,担心门槛高,无法迈入数据分析的门槛。《谁说菜鸟不会数据分析(全彩)》在降低学习难度方面做了大量的尝试:基于通用的Excel工具,加上必知必会的数据分析概念,并且采用通俗易懂的讲解方式。《谁说菜鸟不会数据分析(全彩)》努力将数据分析写成像小说一样通俗易懂,使读者可以在无形之中学会数据分析。《谁说菜鸟不会数据分析(全彩)》按照数据......一起来看看 《谁说菜鸟不会数据分析》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具