Tomcat之图文解析Server.xml配置

Tomcat之图文解析Server.xml配置

十月 23, 2018

Tomcat之图文解析Server.xml配置

本文即将介绍的是Server.xml - Tomcat的主配置文件。该文件存放在安装目录下的conf文件夹中。为了让大家有个直观的认识,在此将Tomcat7的默认配置摘录如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version='1.0' encoding='utf-8'?>

<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.JasperListener" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

<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 name="Catalina">

<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">

<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>

<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log." suffix=".txt"
pattern="%h %l %u %t &quot;%r&quot; %s %b" />
</Host>

</Engine>
</Service>
</Server>

去掉元素属性,稍加整理,配置文件就变成了下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<Server> <!-- 顶级元素 -->
<Listener/> <!-- 组件 -->
<GlobalNamingResources/> <!-- 组件 -->
<Service> <!-- 另一个顶级元素 -->
<Connector/> <!-- 接头 -->
<Engine> <!-- 容器 -->
<Realm/> <!-- 组件 -->
<Host> <!-- 容器 -->
<Valve/> <!-- 组件 -->
<Context></Context> <!-- 容器 -->
</Host>
</Engine>
</Service>
</Server>

根据Tomcat自身的分类,上面这些元素可以分成四种:

  1. 顶级元素 - 即Server和Service,前者是本配置文件的根节点,后者则起到了一个包裹内部组件的作用。换句话说,它们都属于“单纯的、不具有具体职能的配置节点”。
  2. 容器 - Tomcat内真正负责处理请求并返回结果的组件。
  3. Connector - Connector(接头)是一个特殊的组件,如果说整个Tomcat是一堵墙,它就是安装在墙上的插座。外部的客户端通过这些插座来跟Tomcat建立联系。
  4. 嵌套组件 - 这是一些嵌套在各种容器内部的组件,有些可以放在任意一层容器内,例如Listener;有些只能放在固定的位置,例如GlobalNamingResources。

下面将选择性地介绍这些元素。

一、顶级元素

1. Server

一个Tomcat只有一个Server.xml,即一个Tomcat实例只有一个Server。从实现来看,作为Server.xml的根节点,它不是一个容器,它只是单纯地扮演着一个包裹的角色。从命名来看,它又可以是整个Tomcat所有容器和作用于所用容器的组件的混合体。在此,我们把它当做是Tomcat实例本身。

★ 单机Tomcat实例个数

大部分情况下我们在一台服务器/云服务器上只跑一个tomcat,但也有两种特例:

一是为了测试,在一台机器上跑多个Tomcat,用以模拟多台服务器多个应用交互的效果。

二是通过nginx+多个tomcat组成负载均衡集群,以达到以下目的:

  1. 在32位系统上,由于寻址空间、内核预留内存等我们不太需要关心的机制,单个进程可以使用的内存上限大概是1.5G~2G(windows)和2G~3G(linux)。因此,大内存服务器上如果要充分利用内存就只能多开Tomcat。理论上64位系统没有这个问题。
  2. 配置太大的内存可能会降低JVM进行垃圾回收的频率,也就意味着每一次垃圾回收都是一个耗时日久的大工程,甚至可能导致宕机。一般建议单个JVM最大内存不超过1.5G。
  3. 从稳定性上考虑,个别tomcat挂了也不会导致服务下线。

Nginx+Tomcat

★ Server元素属性

1
<Server port="8005" shutdown="SHUTDOWN" />

Server可配置的属性很少。根据上面摘录的默认配置,Server会侦听localhost的TCP端口8005,当该端口接收到字符串”SHUTDOWN”时,即执行关闭Tomcat操作。

★ Server的嵌套组件

Server有两种特有的组件,一个是GlobalNamingResources(全局命名资源),一个是Service(服务)。除此之外,还可以有Listener(监听器)这种可以作用于不同层次容器的组件。Server默认配置了六种Listener,其作用会在后面介绍。配置在Server这一层的Listener对所有容器起作用。

Server元素组件

2. Service

Service是另一个混合体。一个Service就是一个完整的服务,负责将若干个Connector和一个Engine(引擎)包裹在一起。除此之外,Service还可以配置一个Executor(共享线程池)用于管理所有Connector的线程数量。

★ service元素属性

1
<Service name="Catalina">...</Service>

Service的属性比Server更少,一般只用到了name这个属性。默认配置定义了一个名为“Catalina”的 Service。什么是Catalina呢?Cataline是一个太平洋小岛的名字,而Tomcat开发团队的一个核心人员Craig McClanahan喜欢这个小岛,所以…

★ Service的嵌套组件

Service元素组件

如图所示,Service有Executor、Connector和Engine三种组件。其中,每个Connector负责侦听一个TCP端口,接收相应的请求,并转发给绑定的Engine处理。Engine处理完后,通过Connector把结果返回给客户端。在配置了Executor的情况下,所有Connector的线程受Executor统一管理。

二、容器

Tomcat里有四种层次的容器:Engine、Cluster、Host、Context,从属关系为:

Context -> (Cluster in Host) -> Host -> (Cluster in Engine) -> Engine

具体关系如下图所示:

Tomcat容器层次

1. Engine容器

Engine是Service的请求处理引擎,负责处理所有Connector发过来的请求,并将内部处理完毕的结果返回给Connector。它是最外层的容器。

★ Engine元素属性

1
<Engine name="Catalina" defaultHost="localhost" />

■ Engine.name - 引擎的名称

默认配置定义了一个Engine,和外层的Service一样,也叫Catalina。反正一个Service只有一个Engine,这么起名问题也不大。

■ Engine.defaultHost - 默认采用哪一个子容器Host来处理请求

■ Engine.jvmRoute - 一个用于负载均衡场景下的唯一标识符

默认的负载均衡场景下,Tomcat采用一种被称作粘性session的机制管理session信息。

粘性session就是将用户“粘”在其中一个Engine上。比方说,客户端刚访问进来时被Nginx转发到Engine#1并完成了登录验证,那么接下来的每次请求都应该转发到这个Engine。如果该机制失效,客户端的后续访问被Nginx转发到Engine#2的话,由于Engine#2并没有该客户端的session信息,将会要求重新登录。

为了实现粘性session,Engine需要告诉Nginx,某一个session是我处理的,后续这个session都要发给我。方法就是给Engine定义一个唯一的jvmRoute,附在它受理的sessionID上,再返回给Nginx。例如,配置jvmRote=”Engine#1”。更多属性说明请参照官方文档:参考链接

★ Engine上下文配置

每一个Engine都在安装目录的conf文件夹下有一个对应的二级目录,例如默认的conf/cataline。相应的,Engine下的每一个Host,都会在Engine对应的二级目录下有一个三级目录,例如默认的conf/cataline/localhost。其路径规则为:

1
conf/<engine_name>/<host_name>

作用于整个Engine或整个Host的上下文信息可以配置在这些个目录中。该目录可以通过相应容器的xmlBase属性进行修改。

2. Host容器

一个Host就是一个虚拟主机,对应一个或多个域名。

★ Host元素属性

1
2
<Host name="localhost"  appBase="webapps"
unpackWARs="true" autoDeploy="true" />

■ Host.name - 主机名称(域名)

默认配置定义了一个名为 localhost 的主机。至少要有一个Host的名称与Engine的defaultHost一致。

除了域名外,Host可以通过子节点alias来配置别名。别名的作用与域名一致。例如:

1
2
3
4
5
6
7
<Host name="www.a.com" ...>
...
<Alias>a.com</Alias>
<Alias>blog.a.com</Alias>
<Alias>c.com</Alias>
...
</Host>

其作用机制如下图所示:

Engine根据域名转发请求

■ Host.appBase - 虚拟主机的根目录

该参数的默认值为webapps。一般情况下,每一个 webapp 的 URL 和它所在的目录名称相同。以webapps目录下的manager和docs两个程序为例(ROOT是个例外):

1
2
3
4
5
# 目录名即路径名
webapps/manager:http://localhost:8080/manager
webapps/docs:http://localhost:8080/docs
# 特例 - ROOT等于空字符串
webapps/ROOT:http://localhost:8080/

■ Host.unpackWARs - 放到 webapps 目录下的 WAR-file 是否应该被解压

如设为false,Tomcat 将会直接从 *.war文件运行应用,但可能导致应用运行变慢。

■ Host.autoDeploy - 是否自动部署放到 webapps 目录下的应用

3. Context容器

Context代表Host下面的一个虚拟目录。

在不配置Context的情况下是这样的:

1
2
3
4
If HostName:my.oshina.net
-> HostUrl:http://my.oschina.net/
So Brower:http://my.oschina.net/mzdbxqh
-> 访问:webapps/mzdbxqh

现在配置一个Context,同样访问博客域名,就变成了访问”D:/mzdbxqh”

1
<Context path="mzdbxqh" docBase="D:/mzdbxqh" debug="0" reloadable="true" crossContext="true"/>

★ Context元素属性

■ Context.docBase - 应用程序的路径或者是WAR文件存放的路径

■ Context.path - 此web应用程序的上下文路径

配置后,Context的访问url为:http://localhost:8080/path/

path为空的话,访问url为:http://localhost:8080/

■ Context.reloadable - 是否支持热部署

如果为true,则tomcat会自动检测应用程序的/WEB-INF/lib 和/WEB-INF/classes目录的变化,并通过类加载器重新加载class文件,以实现在不重启tomcat的情况下重新部署。

因为配置文件基本是在web程序启动时一次性载入内存的,故一般来说Tomcat的热部署对配置文件无效。另外,只有新创建实例时才会向类加载器请求重新加载过的class,因此,Tomcat的热部署对于使用Spring管理的单例也是无效的。这两种情况需要web应用程序配合着写热部署的业务逻辑才能实现。

关于类加载器的相关内容可以阅读前面的章节

■ Context.crossContext - 不同context是否共享session

注意,这个跨应用共享session跟Cluster集群是不一样的。前者是指可以通过ServletContext.getContext()交叉访问其他Context的session,后者默认配置下是采用服务器session复制的方式,将每一个节点的session变动复制到所有节点。

更多属性说明请参照官方文档:参考链接

4. Cluster容器

Tomcat的Cluster是一个集群容器。它的集群策略是复制session,把集群内一个容器的session变动通过广播复制到其他所有容器上。Cluster可配置在Engine或Host内。如果读者需要使用该容器,一般来说已经不是新手了,请自行阅读官方文档:参考链接

此外,复制session的模式因为对网络(广播复制)和内存(每一个JVM实例都要保存所有在线用户的session)的负担较大,在大型分布式场景下一般采用session共享的方式。具体可以了解一下Spring Session。以上三种session管理方式的对比如下图所示:

session管理

三、其他

1. Connector组件

开头说过,Connector就是墙上的插座,负责侦听一个具体的TCP端口,并通过该端口处理Engine与客户端之间的交互。默认配置定义了两个 Connector:

1
2
3
4
5
6
<!--HTTP/1.1:处理 HTTP 请求,使得 Tomcat 成为了一个 HTTP 服务器。客户端可以通过 Connector 向服务器发送 HTTP 请求,接收服务器端的 HTTP 响应信息。-->
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
<!--connectionTimeOut 属性定义了这个 connector 在链接获得同意之后,获得请求 URI line(请求信息)响应的最大等待时间毫秒数。默认为20秒。redirect 属性会把 SSL 请求重定向到 TCP 的8443端口。-->

<!--AJP/1.3:Apache JServ Protocol connector 处理 Tomcat 服务器与 Apache HTTP 服务器之间的交互。-->
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

更多Connector和Executor线程池的配置会在Tomcat性能调优的章节进行说明。

2. Valve组件

Valve的中文含义是阀门,可以简单地理解为Tomcat的拦截器。它负责在请求发送到应用之前拦截HTTP请求,可以定义在任何容器中。默认配置中定义了一个AccessLogValve,负责拦截HTTP请求,并写入到日志文件中。

1
2
3
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log." suffix=".txt"
pattern="%h %l %u %t &quot;%r&quot; %s %b" />

3. Listener组件

Listener即监听器,负责监听服务器端的行为。此处需要了解的监听器有两个:

1
2
3
4
<!--作用于 Jasper JSP 引擎,该引擎负责对更新后的 JSP 页面进行重编译。-->
<Listener className="org.apache.catalina.core.JasperListener" />
<!--作用于全局资源,保证 JNDI 对资源的可达性,比如数据库。-->
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />

4. Realm组件

Realm提供了一种用户密码与web应用的映射关系。tomcat自带的manager管理工具就是根据conf/tomcat-users.xml中定义的账号来实现登录的。我们一般通过Shiro来实现,此处不作解释。

5. GlobalNamingResources组件

全局命名资源(过JNDI实现,相当于一台交换机,可以通过地址名访问各种系统外部资源。例如,通过一个名称,访问预设置好的一个Mysql连接池)。默认定义了一个名称为UserDatabase的JNDI,通过“conf/tomcat-users.xml”得到了一个用于用户授权的内存数据库。我们一般通过Spring进行管理,此处不作解释。

四、Tomcat处理Http请求的过程

请求地址为:http://my.oschina.net/mzdbxqh

  1. 请求通过DNS发送到指定主机的80端口,被负责侦听80端口的HTTP/1.1 Connector拦截
  2. Connector把请求转发给所在的Service的Engine,并等待Engine的回应
  3. Engine获得请求my.oschina.net/mzdbxqh,匹配它所拥有的所有虚拟主机Host
  4. Engine匹配到名为my.oschina.net的Host
    • 如果匹配不到就交给defaultHost
  5. 匹配的Host获得请求/mzdbxqh,匹配它所拥有的所有Context
  6. Host匹配到path为mzdbxqh的Context
    • 如果匹配不到就尝试匹配Path为””的Context
    • 如果还匹配不到就尝试到appBase参数对应的目录下找mzdbxqh文件夹
  7. 匹配的Context获得请求/,匹配web.xml中配置的Servlet
    • 如果匹配不到就尝试匹配Welcome-file-list
  8. Context匹配到url-pattern为”/“的servlet,对应于org.springframework.web.servlet.DispatcherServlet
  9. SpringMVC处理请求(后面章节再说),返回HttpServletResponse
  10. Context把HttpServletResponse对象返回给Host
  11. Host把HttpServletResponse对象返回给Engine
  12. Engine把HttpServletResponse对象返回给Connector
  13. Connector把HttpServletResponse对象返回给客户端