Tomcat架构设计剖析 | 博学谷狂野架构师

时间:2023-03-24 17:08:13

Tomcat体系架构

Tomcat架构设计剖析 | 博学谷狂野架构师

Tomcat项目结构

Tomcat架构设计剖析 | 博学谷狂野架构师

bin目录

bin目录主要是用来存放tomcat的命令,主要有两大类,一类是以.sh结尾的(linux命令),另一类是以.bat结尾的(windows命令)。

很多环境变量的设置都在此处,例如可以设置JDK路径、tomcat路径

Tomcat架构设计剖析 | 博学谷狂野架构师

  • startup文件:主要是检查catalina.bat/sh 执行所需环境,并调用catalina.bat 批处理文件。启动tomcat。
  • catalina文件:真正启动Tomcat文件,可以在里面设置jvm参数。后面性能调优会重点讲
  • shutdown文件:关闭Tomcat
  • 脚本version.sh、startup.sh、shutdown.sh、configtest.sh都是对catalina.sh的包装,内容大同小异,差异在于功能介绍和调用catalina.sh时的参数不同。
  • Version:查看当前tomcat的版本号,
  • Configtest:校验tomcat配置文件server.xml的格式、内容等是否合法、正确。
  • Service:安装tomcat服务,可用net start tomcat 启动

conf目录

conf目录主要是用来存放tomcat的一些配置文件。

Tomcat架构设计剖析 | 博学谷狂野架构师

  • server.xml:可以设置端口号、设置域名或IP、默认加载的项目、请求编码
  • web.xml:可以设置tomcat支持的文件类型
  • context.xml:可以用来配置数据源之类的
  • tomcat-users.xml:用来配置管理tomcat的用户与权限
  • 在Catalina目录下可以设置默认加载的项目
server.xml
COPY<?xml version="1.0" encoding="UTF-8"?>

<!-- Server代表一个 Tomcat 实例。可以包含一个或多个 Services,其中每个Service都有自己的Engines和Connectors。
       port="8005"指定一个端口,这个端口负责监听关闭tomcat的请求
  -->
<Server port="8005" shutdown="SHUTDOWN">
<!-- 监听器 -->
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<!-- 全局命名资源,定义了UserDatabase的一个JNDI(java命名和目录接口),通过pathname的文件得到一个用户授权的内存数据库 -->
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
               type="org.apache.catalina.UserDatabase"
               description="User database that can be updated and saved"
               factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
               pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<!-- Service它包含一个<Engine>元素,以及一个或多个<Connector>,这些Connector元素共享用同一个Engine元素 -->
<Service name="Catalina">
<!-- 
         每个Service可以有一个或多个连接器<Connector>元素,
         第一个Connector元素定义了一个HTTP Connector,它通过8080端口接收HTTP请求;第二个Connector元素定
         义了一个JD Connector,它通过8009端口接收由其它服务器转发过来的请求.
     -->
<Connector port="8080" protocol="HTTP/1.1"
                connectionTimeout="20000"
                redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<!-- 每个Service只能有一个<Engine>元素 -->
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
                resourceName="UserDatabase"/>
</Realm>
<!-- 默认host配置,有几个域名就配置几个Host,但是这种只能是同一个端口号 -->
<Host name="localhost"  appBase="webapps"
             unpackWARs="true" autoDeploy="true">
       <!-- Tomcat的访问日志,默认可以关闭掉它,它会在logs文件里生成localhost_access_log的访问日志 -->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
                prefix="localhost_access_log" suffix=".txt"
                pattern="%h %l %u %t "%r" %s %b" />
</Host>
<Host name="www.hzg.com"  appBase="webapps"
             unpackWARs="true" autoDeploy="true">
<Context path="" docBase="/myweb1" />
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
                prefix="hzg_access_log" suffix=".txt"
                pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
patter解释

有效的日志格式模式可以参见下面内容,如下字符串,其对应的信息由指定的响应内容取代:

  • %a - 远程IP地址
  • %A - 本地IP地址
  • %b - 发送的字节数,不包括HTTP头,或“ - ”如果没有发送字节
  • %B - 发送的字节数,不包括HTTP头
  • %h - 远程主机名
  • %H - 请求协议
  • %l (小写的L)- 远程逻辑从identd的用户名(总是返回’ - ‘)
  • %m - 请求方法
  • %p - 本地端口
  • %q - 查询字符串(在前面加上一个“?”如果它存在,否则是一个空字符串
  • %r - 第一行的要求
  • %s - 响应的HTTP状态代码
  • %S - 用户会话ID
  • %t - 日期和时间,在通用日志格式
  • %u - 远程用户身份验证
  • %U - 请求的URL路径
  • %v - 本地服务器名
  • %D - 处理请求的时间(以毫秒为单位)
web.xml

Tomcat中所有应用默认的部署描述文件,主要定义了基础的Servlet和MIME映射(mime-mapping 文件类型,其实就是Tomcat处理的文件类型),如果部署的应用中不包含Web.xml,那么Tomcat将使用此文件初始化部署描述,反之,Tomcat会在启动时将默认描述与定义描述配置进行合并。

加载一些tomcat内置的servlet

DefaultServlet默认的,加载静态文件 html,js,jpg等静态文件。

JspServlet专门处理jsp。

context.xml

用于自定义所有Web应用均需要加载的Context配置,如果Web应用指定了自己的context.xml,那么该文件的配置将被覆盖。

context.xml与server.xml中配置context的区别

server.xml是不可动态重加载的资源,服务器一旦启动了以后,要修改这个文件,就得重启服务器才能重新加载。而context.xml文件则不然,tomcat服务器会定时去扫描这个文件。一旦发现文件被修改(时间戳改变了),就会自动重新加载这个文件,而不需要重启服务器。

catalina.policy

权限相关 Permission ,Tomcat是跑在jvm上的,所以有些默认的权限

tomcat-users.xml

配置Tomcat的server的manager信息

COPY<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
              version="1.0">
<role rolename="manager-gui"/>
<user username="manager" password="manager" roles="manager-gui"/>
</tomcat-users>
logging.properties

设置tomcat日志

控制输出不输出内容到文件,不能阻止生成文件,阻止声文件可用注释掉

lib目录

lib目录主要用来存放tomcat运行需要加载的jar包。

例如,像连接数据库的jdbc的包我们可以加入到lib目录中来。

Tomcat的类库,里面是一大堆jar文件。如果需要添加Tomcat依赖的jar文件,可以把它放到这个目录中,当然也可以把应用依赖的jar文件放到这个目录中,这个目录中的jar所有项目都可以共享之,但这样你的应用放到其他Tomcat下时就不能再共享这个目录下的Jar包了,所以建议只把Tomcat需要的Jar包放到这个目录下;

Tomcat架构设计剖析 | 博学谷狂野架构师

logs目录

logs目录用来存放tomcat在运行过程中产生的日志文件,非常重要的是在控制台输出的日志。(清空不会对tomcat运行带来影响)

这个目录中都是日志文件,记录了Tomcat启动和关闭的信息,如果启动Tomcat时有错误,那么异常也会记录在日志文件中

在windows环境中,控制台的输出日志在catalina.xxxx-xx-xx.log文件中 ​ 在linux环境中,控制台的输出日志在catalina.out文件中

  • localhost-xxx.log:Web应用的内部程序日志,建议保留
  • catalina-xxx.log:控制台日志
  • host-manager.xxx.log:Tomcat管理页面中的host-manager的操作日志,建议关闭
  • localhost_access_log_xxx.log:用户请求Tomcat的访问日志(这个文件在conf/server.xml里配置),建议关闭

temp目录

temp目录用户存放tomcat在运行过程中产生的临时文件。(清空不会对tomcat运行带来影响)

Tomcat架构设计剖析 | 博学谷狂野架构师

webapps目录

webapps目录用来存放应用程序,当tomcat启动时会去加载webapps目录下的应用程序。可以以文件夹、war包、jar包的形式发布应用。

当然,你也可以把应用程序放置在磁盘的任意位置,在配置文件中映射好就行。

存放web项目的目录,其中每个文件夹都是一个项目;如果这个目录下已经存在了目录,那么都是tomcat自带的。项目。其中ROOT是一个特殊的项目,在地址栏中没有给出项目目录时,对应的就是ROOT项目。http://localhost:8080/examples,进入示例项目。其中examples就是项目名,即文件夹的名字。

Tomcat架构设计剖析 | 博学谷狂野架构师

work目录

work目录用来存放tomcat在运行时的编译后文件,例如JSP编译后的文件。 清空work目录,然后重启tomcat,可以达到清除缓存的作用。

运行时生成的文件,最终运行的文件都在这里。通过webapps中的项目生成的!可以把这个目录下的内容删除,再次运行时会生再次生成work目录。当客户端用户访问一个JSP文件时,Tomcat会通过JSP生成Java文件,然后再编译Java文件生成class文件,生成的java和class文件都会存放到这个目录下。

Tomcat组件及架构

Tomcat架构设计剖析 | 博学谷狂野架构师

Server

Server是最*的组件,它代表Tomcat的运行实例,它掌管着整个Tomcat的生死大权;

  • 提供了监听器机制,用于在Tomcat整个生命周期中对不同时间进行处理
  • 提供Tomcat容器全局的命名资源实现,JNDI
  • 监听某个端口以接受SHUTDOWN命令,用于关闭Tomcat

Service

一个概念,一个Service维护多个Connector和一个Container

它由一个或者多个Connector组成,以及一个Engine,负责处理所有Connector所获得的客户请求。

Connector组件

链接器:监听转换Socket请求,将请求交给Container处理,支持不同协议以及不同的I/O方式

TOMCAT有两个典型的Connector,一个直接侦听来自browser的http请求,一个侦听来自其它WebServer的请求Coyote Http/1.1 Connector 在端口8080处侦听来自客户browser的http请求Coyote JK2 Connector 在端口8009处侦听来自其它WebServer(Apache)的servlet/jsp代理请求。

Container

表示能够执行客户端请求并返回响应的一类对象,其中有不同级别的容器:Engine、Host、Context、Wrapper

Engine

整个Servler引擎,*的容器对象

Engine下可以配置多个虚拟主机Virtual Host,每个虚拟主机都有一个域名当Engine获得一个请求时,它把该请求匹配到某个Host上,然后把该请求交给该Host来处理Engine有一个默认虚拟主机,当请求无法匹配到任何一个Host上的时候,将交给该默认Host来处理。

Host

表示Servlet引擎中的虚拟机,主要与域名有关,一个服务器有多个域名是可以使用多个Host

代表一个Virtual Host,虚拟主机,每个虚拟主机和某个网络域名Domain Name相匹配,每个虚拟主机下都可以部署(deploy)一个或者多个Web App,每个Web App对应于一个Context,有一个Context path当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给该Context来处理匹配的方法是“最长匹配”,所以一个path==””的Context将成为该Host的默认Context所有无法和其它Context的路径名匹配的请求都将最终和该默认Context匹配。

Context

用于表示ServletContext,一个ServletContext表示一个独立的Web应用

一个Context对应于一个Web Application,一个WebApplication由一个或者多个Servlet组成,Context在创建的时候将根据配置文件$CATALINA_HOME/conf/web.xml和$WEBAPP_HOME/WEB-INF/web.xml载入Servlet类,当Context获得请求时,将在自己的映射表(mapping table)中寻找相匹配的Servlet类。如果找到,则执行该类,获得请求的回应,并返回。

是Web应用的抽象,Web应用部署到Tomcat后运行时就会转化成Context对象;包含了各种静态资源、若干Servlet(Wrapper容器)以及各种其他动态资源;

  • 包含Listener组件用以在生命周期中对Context相关的事件进行监听;
  • 包含AccessLog组件以记录访问日志;
  • 包含Pipeline组件用以处理请求;
  • 包含Realm组件用以提供安全权限功能;
  • 包含Loader组件用以加载Web应用的资源,保证不同Web应用之间的资源隔离;
  • 包含Manager组件用以管理Web容器的会话,包括维护会话的生成、更新和销毁;
  • 包含NamingResource组件将Tomcat配置文件的server.xml和Web应用的context.xml资源和属性映射到内存中;

Wrapper

用于表示Web应用中定义的Servlet

对应的是Servlet;包含Web应用开发常用的Servlet组件;包含ServletPool组件用以存放Servlet对象,当Web应用的Servlet实现了SingleThreadModel接口时则会再Wrapper中产生一个Servlet对象池,线程执行时,需先从对象池中获取到一个Servlet对象,ServletPool组件能保证Servlet对象的线程安全;包含Pipeline组件用以处理请求。

我们从功能的角度将Tomcat源代码分成5个子模块,它们分别是:

  1. Jsper子模块:这个子模块负责jsp页面的解析、jsp属性的验证,同时也负责将jsp页面动态转换为java代码并编译成class文件。在Tomcat源代码中,凡是属于org.apache.jasper包及其子包中的源代码都属于这个子模块;
  2. Servlet和Jsp规范的实现模块:这个子模块的源代码属于javax.servlet包及其子包,如我们非常熟悉的javax.servlet.Servlet接口、javax.servet.http.HttpServlet类及javax.servlet.jsp.HttpJspPage就位于这个子模块中;
  3. Catalina子模块:这个子模块包含了所有以org.apache.catalina开头的java源代码。该子模块的任务是规范了Tomcat的总体架构,定义了Server、Service、Host、Connector、Context、Session及Cluster等关键组件及这些组件的实现,这个子模块大量运用了Composite设计模式。同时也规范了Catalina的启动及停止等事件的执行流程。从代码阅读的角度看,这个子模块应该是我们阅读和学习的重点。
  4. Connectors子模块:如果说上面三个子模块实现了Tomcat应用服务器的话,那么这个子模块就是Web服务器的实现。所谓连接器(Connector)就是一个连接客户和应用服务器的桥梁,它接收用户的请求,并把用户请求包装成标准的Http请求(包含协议名称,请求头Head,请求方法是Get还是Post等等)。同时,这个子模块还按照标准的Http协议,负责给客户端发送响应页面,比如在请求页面未发现时,connector就会给客户端浏览器发送标准的Http 404错误响应页面。
  5. Resource子模块:这个子模块包含一些资源文件,如Server.xml及Web.xml配置文件。严格说来,这个子模块不包含java源代码,但是它还是Tomcat编译运行所必需的。

Executor

Tomcat组件间可以共享的线程池

Tomcat的并发,提供了Executor接口来表示一个可以在组件间共享的线程池。该接口同样继承LifeCycle接口

共享范围:Executor由Service维护,因此同一个Service中的组件可以共享一个线程池

Tomcat的核心组件

  • 解耦:网络协议与容器的解耦。
  • Connector:链接器封装了底层的网络请求(Socket请求及相应处理),提供了统一的接口,使Container容器与具体的请求协议以及I/O方式解耦。
  • Connector:将Socket输入转换成Request对象,交给Container容器进行处理,处理请求后,Container通过Connector提供的Response对象将结果写入输出流。

因为无论是Request对象还是Response对象都没有实现Servlet规范对应的接口,Container会将它们进一步分装成ServletRequest和ServletResponse.

Tomcat的链接器

AJP主要是用于Web服务器与Tomcat服务器集成,AJP采用二进制传输可读性文本,使用保持持久性的TCP链接,使得AJP占用更少的带宽,并且链接开销要小得多,但是由于AJP采用持久化链接,因此有效的连接数较HTTP要更多。

对于I/0选择,要根据业务场景来定,一般高并发场景下,APR和NIO2的性能要优于NIO和BIO,(linux操作系统支持的NIO2由于是一个假的,并没有真正实现AIO,所以一般linux上推荐使用NIO,如果是APR的话,需要安装APR库,而Windows上默认安装了),所以在8.5的版本中默认是NIO。

Tomcat运行流程

假设来自客户的请求为 http://localhost:8080/test/index.jsp

  1. 请求被发送到本机端口8080,被在那里侦听的Coyote HTTP/1.1 Connector获得
  2. Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的回应
  3. Engine获得请求localhost:8080/test/index.jsp,匹配它所有虚拟主机Host
  4. Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机)
  5. localhost Host获得请求/test/index.jsp,匹配它所拥有的所有Context
  6. Host匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为””的Context去处理)
  7. path=”/test”的Context获得请求/index.jsp,在它的mapping table中寻找对应的servlet
  8. Context匹配到URL PATTERN为*.jsp的servlet,对应于JspServlet类
  9. 构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet或doPost方法
  10. Context把执行完了之后的HttpServletResponse对象返回给Host
  11. Host把HttpServletResponse对象返回给Engine
  12. Engine把HttpServletResponse对象返回给Connector
  13. Connector把HttpServletResponse对象返回给客户browser

本文由传智教育博学谷狂野架构师教研团队发布。

如果本文对您有帮助,欢迎关注点赞;如果您有任何建议也可留言评论私信,您的支持是我坚持创作的动力。

转载请注明出处!