tomcat体系架构
tomcat体系架构
tomcat项目结构
bin目录
bin目录主要是用来存放tomcat的命令,主要有两大类,一类是以.sh结尾的(linux命令),另一类是以.bat结尾的(windows命令)。
很多环境变量的设置都在此处,例如可以设置JDK路径、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的一些配置文件。
- server.xml:可以设置端口号、设置域名或IP、默认加载的项目、请求编码
- web.xml:可以设置tomcat支持的文件类型
- context.xml:可以用来配置数据源之类的
- tomcat-users.xml:用来配置管理tomcat的用户与权限
- 在Catalina目录下可以设置默认加载的项目
server.xml
<?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信息
<?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包放到这个目录下;
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运行带来影响)
webapps目录
webapps目录用来存放应用程序,当tomcat启动时会去加载webapps目录下的应用程序。可以以文件夹、war包、jar包的形式发布应用。
当然,你也可以把应用程序放置在磁盘的任意位置,在配置文件中映射好就行。
存放web项目的目录,其中每个文件夹都是一个项目;如果这个目录下已经存在了目录,那么都是tomcat自带的。项目。其中ROOT是一个特殊的项目,在地址栏中没有给出项目目录时,对应的就是ROOT项目。http://localhost:8080/examples,进入示例项目。其中examples就是项目名,即文件夹的名字。
work目录
work目录用来存放tomcat在运行时的编译后文件,例如JSP编译后的文件。
清空work目录,然后重启tomcat,可以达到清除缓存的作用。
运行时生成的文件,最终运行的文件都在这里。通过webapps中的项目生成的!可以把这个目录下的内容删除,再次运行时会生再次生成work目录。当客户端用户访问一个JSP文件时,tomcat会通过JSP生成Java文件,然后再编译Java文件生成class文件,生成的java和class文件都会存放到这个目录下。
tomcat组件及架构
tomcat 通过一种分层的架构,使得 Servlet 容器具有很好的灵活性。
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个子模块,它们分别是:
- Jsper子模块:这个子模块负责jsp页面的解析、jsp属性的验证,同时也负责将jsp页面动态转换为java代码并编译成class文件。在tomcat源代码中,凡是属于org.apache.jasper包及其子包中的源代码都属于这个子模块;
- Servlet和Jsp规范的实现模块:这个子模块的源代码属于javax.servlet包及其子包,如我们非常熟悉的javax.servlet.Servlet接口、javax.servet.http.HttpServlet类及javax.servlet.jsp.HttpJspPage就位于这个子模块中;
- Catalina子模块:这个子模块包含了所有以org.apache.catalina开头的java源代码。该子模块的任务是规范了tomcat的总体架构,定义了Server、Service、Host、Connector、Context、Session及Cluster等关键组件及这些组件的实现,这个子模块大量运用了Composite设计模式。同时也规范了Catalina的启动及停止等事件的执行流程。从代码阅读的角度看,这个子模块应该是我们阅读和学习的重点。
- Connectors子模块:如果说上面三个子模块实现了tomcat应用服务器的话,那么这个子模块就是Web服务器的实现。所谓连接器(Connector)就是一个连接客户和应用服务器的桥梁,它接收用户的请求,并把用户请求包装成标准的Http请求(包含协议名称,请求头Head,请求方法是Get还是Post等等)。同时,这个子模块还按照标准的Http协议,负责给客户端发送响应页面,比如在请求页面未发现时,connector就会给客户端浏览器发送标准的Http 404错误响应页面。
- 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/O选择,要根据业务场景来定,一般高并发场景下,APR和NIO2的性能要优于NIO和BIO,(linux操作系统支持的NIO2由于是一个假的,并没有真正实现AIO,所以一般linux上推荐使用NIO,如果是APR的话,需要安装APR库,而Windows上默认安装了),所以在8.5的版本中默认是NIO。
那么,tomcat 是怎么管理这些容器的呢?你会发现这些容器具有父子关系,形成一个树形结构,你可能马上就想到了设计模式中的组合模式。没错,tomcat 就是用组合模式来管理这些容器的。具体实现方法是,所有容器组件都实现了 Container 接口,因此组合模式可以使得用户对单容器对象和组合容器对象的使用具有一致性。这里单容器对象指的是最底层的 Wrapper,组合容器对象指的是上面的 Context、Host 或者 Engine。Container 接口定义如下:
public interface Container extends Lifecycle {
public void setName(String name);
public Container getParent();
public void setParent(Container container);
public void addChild(Container child);
public void removeChild(Container child);
public Container findChild(String name);
}
正如我们期望的那样,我们在上面的接口看到了 getParent、SetParent、addChild 和 removeChild 等方法。你可能还注意到 Container 接口扩展了 LifeCycle 接口,LifeCycle 接口用来统一管理各组件的生命周期。
请求定位 Servlet 的过程
设计了这么多层次的容器,tomcat 是怎么确定请求是由哪个 Wrapper 容器里的 Servlet 来处理的呢?答案是,tomcat 是用 Mapper 组件来完成这个任务的。
Mapper 组件的功能就是将用户请求的 URL 定位到一个 Servlet,它的工作原理是:Mapper 组件里保存了 Web 应用的配置信息,其实就是容器组件与访问路径的映射关系,比如 Host 容器里配置的域名、Context 容器里的 Web 应用路径,以及 Wrapper 容器里 Servlet 映射的路径,你可以想象这些配置信息就是一个多层次的 Map。
当一个请求到来时,Mapper 组件通过解析请求 URL 里的域名和路径,再到自己保存的 Map 里去查找,就能定位到一个 Servlet。请你注意,一个请求 URL 最后只会定位到一个 Wrapper 容器,也就是一个 Servlet。
假如有一个网购系统,有面向网站管理人员的后台管理系统,还有面向终端客户的在线购物系统。这两个系统跑在同一个 tomcat 上,为了隔离它们的访问域名,配置了两个虚拟域名:manage.shopping.com
和user.shopping.com
,网站管理人员通过manage.shopping.com
域名访问 tomcat 去管理用户和商品,而用户管理和商品管理是两个单独的 Web 应用。终端客户通过user.shopping.com
域名去搜索商品和下订单,搜索功能和订单管理也是两个独立的 Web 应用。
针对这样的部署,tomcat 会创建一个 Service 组件和一个 Engine 容器组件,在 Engine 容器下创建两个 Host 子容器,在每个 Host 容器下创建两个 Context 子容器。由于一个 Web 应用通常有多个 Servlet,tomcat 还会在每个 Context 容器里创建多个 Wrapper 子容器。每个容器都有对应的访问路径,你可以通过下面这张图来帮助你理解。
假如有用户访问一个 URL,比如图中的http://user.shopping.com:8080/order/buy
,tomcat 如何将这个 URL 定位到一个 Servlet 呢?
首先,根据协议和端口号选定 Service 和 Engine。
我们知道 tomcat 的每个连接器都监听不同的端口,比如 tomcat 默认的 HTTP 连接器监听 8080 端口、默认的 AJP 连接器监听 8009 端口。上面例子中的 URL 访问的是 8080 端口,因此这个请求会被 HTTP 连接器接收,而一个连接器是属于一个 Service 组件的,这样 Service 组件就确定了。我们还知道一个 Service 组件里除了有多个连接器,还有一个容器组件,具体来说就是一个 Engine 容器,因此 Service 确定了也就意味着 Engine 也确定了。
然后,根据域名选定 Host。
Service 和 Engine 确定后,Mapper 组件通过 URL 中的域名去查找相应的 Host 容器,比如例子中的 URL 访问的域名是user.shopping.com
,因此 Mapper 会找到 Host2 这个容器。
之后,根据 URL 路径找到 Context 组件。
Host 确定以后,Mapper 根据 URL 的路径来匹配相应的 Web 应用的路径,比如例子中访问的是 /order,因此找到了 Context4 这个 Context 容器。
最后,根据 URL 路径找到 Wrapper(Servlet)。
Context 确定后,Mapper 再根据 web.xml 中配置的 Servlet 映射路径来找到具体的 Wrapper 和 Servlet。
tomcat运行流程
假设来自客户的请求为 http://localhost:8080/test/index.jsp
- 请求被发送到本机端口8080,被在那里侦听的Coyote HTTP/1.1 Connector获得
- Connector把该请求交给它所在的Service的Engine来处理,并等待Engine的回应
- Engine获得请求localhost:8080/test/index.jsp,匹配它所有虚拟主机Host
- Engine匹配到名为localhost的Host(即使匹配不到也把请求交给该Host处理,因为该Host被定义为该Engine的默认主机)
- localhost Host获得请求/test/index.jsp,匹配它所拥有的所有Context
- Host匹配到路径为/test的Context(如果匹配不到就把该请求交给路径名为””的Context去处理)
- path=”/test”的Context获得请求/index.jsp,在它的mapping table中寻找对应的servlet
- Context匹配到URL PATTERN为*.jsp的servlet,对应于JspServlet类
- 构造HttpServletRequest对象和HttpServletResponse对象,作为参数调用JspServlet的doGet或doPost方法
- Context把执行完了之后的HttpServletResponse对象返回给Host
- Host把HttpServletResponse对象返回给Engine
- Engine把HttpServletResponse对象返回给Connector
- Connector把HttpServletResponse对象返回给客户browser