本文隶属于新手图文教程《IDEA+Maven搭建Spring+SpringMVC+Mybatis项目骨架》
下一篇:打算放弃这个系列,Spring配置太多不适合新手,改用SpringBoot
本文即将介绍的是Server.xml - Tomcat的主配置文件。该文件存放在安装目录下的conf文件夹中。为了让大家有个直观的认识,在此将Tomcat7的默认配置摘录如下:
<?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 "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
去掉元素属性,稍加整理,配置文件就变成了下面这样:
<Server> <!-- 顶级元素 -->
<Listener/> <!-- 组件 -->
<GlobalNamingResources/> <!-- 组件 -->
<Service> <!-- 另一个顶级元素 -->
<Connector/> <!-- 接头 -->
<Engine> <!-- 容器 -->
<Realm/> <!-- 组件 -->
<Host> <!-- 容器 -->
<Valve/> <!-- 组件 -->
<Context></Context> <!-- 容器 -->
</Host>
</Engine>
</Service>
</Server>
根据Tomcat自身的分类,上面这些元素可以分成四种:
- 顶级元素 - 即Server和Service,前者是本配置文件的根节点,后者则起到了一个包裹内部组件的作用。换句话说,它们都属于“单纯的、不具有具体职能的配置节点”。
- 容器 - Tomcat内真正负责处理请求并返回结果的组件。
- Connector - Connector(接头)是一个特殊的组件,如果说整个Tomcat是一堵墙,它就是安装在墙上的插座。外部的客户端通过这些插座来跟Tomcat建立联系。
- 嵌套组件 - 这是一些嵌套在各种容器内部的组件,有些可以放在任意一层容器内,例如Listener;有些只能放在固定的位置,例如GlobalNamingResources。
下面将选择性地介绍这些元素。
一、顶级元素
1. Server
一个Tomcat只有一个Server.xml,即一个Tomcat实例只有一个Server。从实现来看,作为Server.xml的根节点,它不是一个容器,它只是单纯地扮演着一个包裹的角色。从命名来看,它又可以是整个Tomcat所有容器和作用于所用容器的组件的混合体。在此,我们把它当做是Tomcat实例本身。
★ 单机Tomcat实例个数
大部分情况下我们在一台服务器/云服务器上只跑一个tomcat,但也有两种特例:
一是为了测试,在一台机器上跑多个Tomcat,用以模拟多台服务器多个应用交互的效果。
二是通过nginx+多个tomcat组成负载均衡集群,以达到以下目的:
- 在32位系统上,由于寻址空间、内核预留内存等我们不太需要关心的机制,单个进程可以使用的内存上限大概是1.5G~2G(windows)和2G~3G(linux)。因此,大内存服务器上如果要充分利用内存就只能多开Tomcat。理论上64位系统没有这个问题。但由于内存指针膨胀和数据类型对齐补白等同样不太需要关心的机制,运行在64位系统上的JVM需要消耗更多的内存(《深入理解Java虚拟机》),因此,多数应用还是采用在32位系统上部署虚拟集群的方式。
- 配置太大的内存可能会降低JVM进行垃圾回收的频率,也就意味着每一次垃圾回收都是一个耗时日久的大工程,甚至可能导致宕机。一般建议单个JVM最大内存不超过1.5G。
- 从稳定性上考虑,个别tomcat挂了也不会导致服务下线。
★ Server元素属性
<Server port="8005" shutdown="SHUTDOWN" />
Server可配置的属性很少。根据上面摘录的默认配置,Server会侦听localhost的TCP端口8005,当该端口接收到字符串"SHUTDOWN"时,即执行关闭Tomcat操作。
★ Server的嵌套组件
Server有两种特有的组件,一个是GlobalNamingResources(全局命名资源),一个是Service(服务)。除此之外,还可以有Listener(监听器)这种可以作用于不同层次容器的组件。Server默认配置了六种Listener,其作用会在后面介绍。配置在Server这一层的Listener对所有容器起作用。
2. Service
Service是另一个混合体。一个Service就是一个完整的服务,负责将若干个Connector和一个Engine(引擎)包裹在一起。除此之外,Service还可以配置一个Executor(共享线程池)用于管理所有Connector的线程数量。
★ service元素属性
<Service name="Catalina">...</Service>
Service的属性比Server更少,一般只用到了name这个属性。默认配置定义了一个名为“Catalina”的 Service。什么是Catalina呢?Cataline是一个太平洋小岛的名字,而Tomcat开发团队的一个核心人员Craig McClanahan喜欢这个小岛,所以...
★ 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
具体关系如下图所示:
1. Engine容器
Engine是Service的请求处理引擎,负责处理所有Connector发过来的请求,并将内部处理完毕的结果返回给Connector。它是最外层的容器。
★ Engine元素属性
<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。其路径规则为:
conf/<engine_name>/<host_name>
作用于整个Engine或整个Host的上下文信息可以配置在这些个目录中。该目录可以通过相应容器的xmlBase属性进行修改。
2. Host容器
一个Host就是一个虚拟主机,对应一个或多个域名。
★ Host元素属性
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true" />
■ Host.name - 主机名称(域名)
默认配置定义了一个名为 localhost 的主机。至少要有一个Host的名称与Engine的defaultHost一致。
除了域名外,Host可以通过子节点alias来配置别名。别名的作用与域名一致。例如:
<Host name="www.a.com" ...>
...
<Alias>a.com</Alias>
<Alias>blog.a.com</Alias>
<Alias>c.com</Alias>
...
</Host>
其作用机制如下图所示:
■ Host.appBase - 虚拟主机的根目录
该参数的默认值为webapps。一般情况下,每一个 webapp 的 URL 和它所在的目录名称相同。以webapps目录下的manager和docs两个程序为例(ROOT是个例外):
# 目录名即路径名
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的情况下是这样的:
If HostName:my.oshina.net
-> HostUrl:http://my.oschina.net/
So Brower:http://my.oschina.net/mzdbxqh
-> 访问:webapps/mzdbxqh
现在配置一个Context,同样访问博客域名,就变成了访问"D:/mzdbxqh"
<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管理方式的对比如下图所示:
三、其他
1. Connector组件
开头说过,Connector就是墙上的插座,负责侦听一个具体的TCP端口,并通过该端口处理Engine与客户端之间的交互。默认配置定义了两个 Connector:
<!--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请求,并写入到日志文件中。
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log." suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
3. Listener组件
Listener即监听器,负责监听服务器端的行为。此处需要了解的监听器有两个:
<!--作用于 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
- 请求通过DNS发送到指定主机的80端口,被负责侦听80端口的HTTP/1.1 Connector拦截
- Connector把请求转发给所在的Service的Engine,并等待Engine的回应
- Engine获得请求my.oschina.net/mzdbxqh,匹配它所拥有的所有虚拟主机Host
- Engine匹配到名为my.oschina.net的Host
- 如果匹配不到就交给defaultHost
- 匹配的Host获得请求/mzdbxqh,匹配它所拥有的所有Context
- Host匹配到path为mzdbxqh的Context
- 如果匹配不到就尝试匹配Path为""的Context
- 如果还匹配不到就尝试到appBase参数对应的目录下找mzdbxqh文件夹
- 匹配的Context获得请求/,匹配web.xml中配置的Servlet
- 如果匹配不到就尝试匹配Welcome-file-list
- Context匹配到url-pattern为"/"的servlet,对应于org.springframework.web.servlet.DispatcherServlet
- SpringMVC处理请求(后面章节再说),返回HttpServletResponse
- Context把HttpServletResponse对象返回给Host
- Host把HttpServletResponse对象返回给Engine
- Engine把HttpServletResponse对象返回给Connector
- Connector把HttpServletResponse对象返回给客户端
太长了先不画图了,就先这样吧。
(本文内容参考了众多博文,内容散乱,来源不可考)