序
身为 Java
程序员,Tomcat
应该算是我们接触的最多的 web
容器了。同时,作为企业生产工具的 “八阿哥”
们,平常只顾着埋头写 BUG
,哪有什么时间整理用过的知识点。今天,我将和大家一起梳理一下关于 Tomcat
的相关内容,由浅入深,从入门到放弃。奥利给~!
一、HTTP 请求处理流程
如上图所示:
- 用户通过浏览器发起请求。
- 浏览器向目标服务器发起
TCP
连接请求。 - 经过三次握手后客户端浏览器与目标服务器成功建立
TCP
连接。(基于Socket
实现) - 浏览器生成
HTTP
格式的数据包。 - 浏览器发送
TCP
请求数据包。(这个数据包的请求头为TCP
格式的请求头,上一步封装的HTTP
格式的数据包被作为TCP
请求体传送至服务器) - 服务器解析
TCP
请求体中的HTTP
数据包。 - 服务器处理请求,完成相关业务逻辑。
- 服务器生成
HTTP
格式的响应数据包。 - 服务器将响应数据包发送至客户端浏览器。
- 浏览器解析
HTTP
格式的响应数据包。(解析出的数据为静态数据如:HTML
、CSS
、JS
、图片等) - 浏览器渲染响应结果,呈现静态数据给用户。
二、Tomcat 总体架构说明
Tomcat
作为 Servlet
容器从软件运行角度看,主要可以分为两大模块,一个是 Http
请求接收、响应模块(Coyote
),另一个是请求处理模块,即从 Servlet
容器中获取与请求对应的处理方法并执行(Catalina
)。
Coyote
连接器包含以下两个模块:
ProtocolHandler
协议处理接口。这个接口通过Endpoint
和Processor
,实现针对具体协议的处
理能⼒。Tomcat
按照协议和I/O
提供了6个实现类 :AjpNioProtocol
,AjpAprProtocol
,AjpNio2Protocol
,Http11NioProtocol
,Http11Nio2Protocol
,Http11AprProtocol
-
EndPoint
是用来实现TCP/IP
协议的,是Coyote
通信的端点,即通信监听的接⼝。是具体Socket
接收和发送处理器。EndPoint
是对传输层的抽象。 -
Processor
⽤来实现HTTP
协议,可将来⾃EndPoint
的Socket
,读取字节流解析成Tomcat
自己定义的Request
和Response
对象。Processor
是对应⽤层协议的抽象(注意:这里的Tomcat Request
和Response
对象,还不是我们在Servlet
中所用到的ServletRequest
和ServletResponse
对象。)
Adapter
由于请求协议的不同,客户端发送至后台的请求信息也不尽相同。为了能够转换成统一的ServletRequest
和ServletResponse
对象方便Servlet
容器处理业务,则需要该接口的实现类,对发送至Tomcat
的请求进行统一的适配处理。进而使调用Servlet
容器中的方法变的简单。
Servlet
容器又名 Catalina
。一个 Catalina
实例只能拥有一个 Server
实例,一个 Server
实例可以包含多个 Service
实例。而每一个 Service
实例又可以包含多个 Connector
和一个 Container
。一个 Container
只能有一个 Engine
,一个 Engine
可以有多个 Host
,每个 Host
可以包含多个 Context
(网站),每个 Context
中可以有多个 Wrapper
(Servlet
)。
Catalina
实例可以被看成就是一个Tomcat
实例。该实例用以解析server.xml
配置文件, 以此来创建服务器Server组件并进⾏管理。Server
表示整个Catalina Servlet
容器以及其它组件,负责组装并启动Servlaet
引擎,Tomcat
连接器。Service
是Server
内部的组件,⼀个Server
包含多个Service
。它将若⼲个Connector
组件绑定到⼀个Container
。(通常情况下只用一个Service
就够了)Container
负责处理⽤户的servlet
请求,并将处理结果返回给用户。
Engine
是整个Catalina
的Servlet
引擎,⽤来管理多个虚拟站点,⼀个Service
最多只能有⼀个Engine
,但是⼀个Engine
可包含多个Host
。Host
代表⼀个虚拟主机,或者说⼀个站点,可以给Tomcat
配置多个虚拟主机地址。⽽⼀个虚拟主机下可包含多个Context
。Context
表示⼀个Web
应⽤程序, ⼀个Web
应⽤可包含多个Wrapper
。Wrapper
表示⼀个Servlet
。Wrapper
作为容器中的最底层,不能包含⼦容器。
Tips:
上述组件的配置其实就体现在 Tomcat
软件中的 conf/server.xml
里。
三、Tomcat 核心配置文件 server.xml
问个实在的问题:当你第一次甚至说上一次看 Tomcat
的配置文件 server.xml
时,你的第一感受是啥?反正我最真切的感受就是:“我靠,英文真多~!”。不过静下心来再看,更多的都是说明性文字。删除注释后再看,就跟被拔光毛的鸡一样,也就那样。不信你看
<?xml version="1.0" encoding="UTF-8"?>
<!-- port:关闭服务器的监听端⼝ shutdown:关闭服务器的指令字符串 -->
<Server port="8005" shutdown="SHUTDOWN">
<!-- 以⽇志形式输出服务器 、操作系统、JVM的版本信息 -->
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<!-- 加载(服务器启动)和销毁(服务器停⽌)APR。如果找不到 APR 库,则会输出⽇志,但并不影响 Tomcat 启动 -->
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<!-- 避免JRE内存泄漏问题 -->
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<!-- 加载(服务器启动)和销毁(服务器停⽌)全局命名服务 -->
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<!-- 在Context停⽌时重建 Executor 池中的线程,以避免 ThreadLocal 相关的内存泄漏 -->
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<!-- 全局命名服务,定义服务器的全局 JNDI 资源 -->
<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">
<!-- 处理 HTTP/1.1 协议的请求 -->
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000"
redirectPort="8443" />
<!-- 处理 AJP/1.3 协议的请求 用不到的话可以注释掉 -->
<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>
这么一整理是不是瞬间清爽了很多。需要注意的是,通常情况下,上面配置文件里添加了注释的部分,除了 Connector
标签中的参数,其他标签的内容一般不需要修改,保持默认即可。
四、核心配置文件进阶
4-1 <Service> 标签
<Service name="Catalina">
...
</Service>
该标签⽤于创建 Service
实例,默认使⽤ org.apache.catalina.core.StandardService
对象。默认情况下,Tomcat
仅指定了 Service
的名称, 值为 “Catalina
”。
Service
⼦标签有 : Listener
、Executor
、Connector
、Engine
。其中:
Listener
⽤于为Service
添加⽣命周期监听器(用的比较少,故下文不做说明)Executor
⽤于配置Service
共享线程池Connector
⽤于配置Service
包含的链接器Engine
⽤于配置Service
中链接器对应的Servlet
容器引擎
4-2 <Executor> 标签
该标签用于为 Service
添加线程池。默认情况下,在 server.xml
文件中,该标签是被注释掉的,即未开启线程池的。如果想开启,则打开注释,并配置相关属性即可。
<Executor name="commonThreadPool"
namePrefix="thread-exec-"
maxThreads="200"
minSpareThreads="100"
maxIdleTime="60000"
maxQueueSize="Integer.MAX_VALUE"
prestartminSpareThreads="false"
threadPriority="5"
className="org.apache.catalina.core.StandardThreadExecutor"/>
<Executor> 标签属性含义:
name
:线程池名称。通过该属性的值,可在<Connector>
标签中指定对应线程池。namePrefix
:所创建的每个线程的名称前缀,⼀个单独的线程名称为:namePrefix + threadNumber
。maxThreads
:线程池中最⼤线程数。minSpareThreads
:最小空闲线程数,即活跃线程数,也就是核⼼池线程数。这些线程不会被销毁,会⼀直存在。maxIdleTime
:线程空闲时间,超过该时间后,空闲线程会被销毁,默认值为6000
(1分钟),单位毫秒。maxQueueSize
:在被执⾏前最⼤线程排队数⽬,默认为Int
的最⼤值,也就是⼴义的⽆限。除⾮特殊情况,否则这个值不需要更改,不然会有请求不会被处理的情况发⽣。prestartminSpareThreads
:启动线程池时是否启动minSpareThreads
部分线程。默认值为false
,即不启动。threadPriority
:线程池中线程优先级,默认值为5
,值从1
到10
。className
:线程池实现类,未指定情况下,默认实现类为org.apache.catalina.core.StandardThreadExecutor
。如果想使⽤⾃定义线程池⾸先需要实现org.apache.catalina.Executor
接⼝。
4-3 <Connector> 标签
该⽤于创建链接器实例默认情况下,server.xml
配置了两个链接器,⼀个⽀持 HTTP
协议,⼀个⽀持 AJP
协议⼤多数情况下,我们并不需要新增链接器配置,只是根据需要对已有链接器进⾏优化。
<Connector port="8080"
protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
executor="commonThreadPool"
URIEncoding="UTF-8"
minSpareThreads="100"
maxThreads="1000"
acceptCount="1000"
maxConnections="1000"
compression="on"
compressionMinSize="2048"
disableUploadTimeout="true"
/>
<Connector> 标签属性含义:
port
:端⼝号,Connector
⽤于创建服务端Socket
并进⾏监听, 以等待客户端请求链接。如果该属性设置为0
,Tomcat
将会随机选择⼀个可⽤的端⼝号给当前Connector
使⽤。protocol
:当前Connector
⽀持的访问协议。 默认为HTTP/1.1
,并采⽤⾃动切换机制选择⼀个基于JAVA NIO
的链接器或者基于本地APR
的链接器。(若想使用APR
提高Tomcat
的并发性能,则需要安装相关运行库后,再进行配置修改)connectionTimeOut
:Connector
接收链接后的等待超时时间, 单位为 毫秒。 -1 表示不超时。redirectPort
:当前Connector
不⽀持SSL
请求。如果接收到了⼀个请求, 且同时符合security-constraint
约束,需要SSL
传输,Catalina
则会⾃动将请求重定向到指定的端⼝。executor
:指定要使用的共享线程池的名称。URIEncoding
:⽤于指定编码URI
的字符编码,Tomcat8.x
版本默认的编码为UTF-8
,Tomcat7.x
版本默认为ISO-8859-1
。- 当我们不使用共享线程池(即不添加
executor
属性),选择让每一个Connector
单独维护自己的线程池时可通过maxThreads
、minSpareThreads
、acceptCount
、maxConnections
进行Connector
线程池的配置。需要注意的是maxThreads
、acceptCount
、maxConnections
这三个属性的值通常保持一致。 compression
:是否开启数据请求等的gzip
格式压缩。compressionMinSize
:最小压缩大小,即当数据请求超过指定值后才会进行压缩。disableUploadTimeout
:允许Servlet
容器,使用较长的连接超时值,以使Servlet
有较长的时间来完成它的执行,默认值为false
。
4-4 <Engine> 标签
<Engine name="Catalina" defaultHost="localhost">
...
</Engine>
该标签表示 Servlet
引擎。其属性 name
⽤于指定 Engine
的名称, 默认为Catalina
。defaultHost
属性用于指定默认使⽤的虚拟主机名称,当客户端请求指向的主机⽆效时,将交由默认的虚拟主机处理,默认为值 localhost
。
4-5 <Host> 标签
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
...
</Host>
该标签用于配置⼀个虚拟主机。
name
:指定虚拟主机的名称。appBase
:指定引用基础目录。unpackWARs
:是否自动解压war
包。autoDeploy
:当资源有变更时是否自动部署。
4-6 <Context> 标签
<Host name="www.abc.com" appBase="webapps" unpackWARs="true" autoDeploy="true">
<Context docBase="/Users/yingdian/web_demo" path="/web_demo"></Context>
<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>
标签中的 <Context>
标签用于配置一个 Web
应用。
docBase
:指定Web
应⽤⽬录或者War
包的部署路径。可以是绝对路径,也可以是相对于Host appBase
的相对路径。(appBase
的子目录)path
:Web
应⽤的URL
访问路径。如上所示,我们Host
名为www.abc.com
, 则该Web
应⽤访问的根路径为:http://www.abc.com:8080/web_demo
。
五、Tomcat 配置 HTTPS
-
使⽤
JDK
中的keytool
⼯具⽣成免费的秘钥库⽂件(证书)。keytool -genkey -alias SupremeSir -keyalg RSA -keystore supreme.keystore
- 配置 server.xml
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" schema="https" secure="true" SSLEnabled="true">
<SSLHostConfig>
<Certificate certificateKeystoreFile="E:\**\apache-tomcat\bin\supreme.keystore" certificateKeystorePassword="123456" type="RSA"/>
</SSLHostConfig>
</Connector>
certificateKeystoreFile
:指定证书所在位置。
certificateKeystorePassword
:指定证书秘钥库口令。
六、Tomcat 性能优化
Tomcat
优化没有明确的参数值可以直接去使⽤,必须根据⾃⼰的真实⽣产环境来进⾏调整,调优是⼀个过程。调优主要从两个⽅⾯进⾏:
JVM
虚拟机优化(优化内存模型)Tomcat
⾃身配置的优化
6-1 Java 虚拟机内存相关参数
参数 | 参数作用 | 优化建议 |
---|---|---|
-server | 启动Server,以服务端模式运⾏ | 服务端模式建议开启 |
-Xms | 最⼩堆内存 | 建议与-Xmx设置相同 |
-Xmx | 最⼤堆内存 | 建议设置为可⽤内存的80% |
-XX:MetaspaceSize | 元空间初始值 | 自行调整 |
-XX:MaxMetaspaceSize | 元空间最⼤内存 | 默认⽆限 |
-XX:NewRatio | 年轻代和⽼年代⼤⼩⽐值,取值为整数,默认为2 | 不需要修改 |
-XX:SurvivorRatio | Eden区与Survivor区⼤⼩的⽐值,取值为整数,默认为8 | 不需要修改 |
在 bin/catalina.sh
的脚本中 , 追加如下配置
JAVA_OPTS="-server -Xms2048m -Xmx2048m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"
6-2 垃圾收集器
-
串⾏收集器(
Serial Collector
)单线程执⾏所有的垃圾回收⼯作, 适⽤于单核CPU服务器。
-
并⾏收集器(
Parallel Collector
)⼜称为吞吐量收集器(关注吞吐量), 以并⾏的⽅式执⾏年轻代的垃圾回收, 该⽅式可以显著降
低垃圾回收的开销(指多条垃圾收集线程并⾏⼯作,但此时⽤户线程仍然处于等待状态)。适⽤于多
处理器或多线程硬件上运⾏的数据量较⼤的应⽤。 -
并发收集器(
Concurrent Collector
)以并发的⽅式执⾏⼤部分垃圾回收⼯作,以缩短垃圾回收的暂停时间。适⽤于那些响应时间优先于
吞吐量的应⽤, 因为该收集器虽然最⼩化了暂停时间(指⽤户线程与垃圾收集线程同时执⾏,但不⼀
定是并⾏的,可能会交替进⾏), 但是会降低应⽤程序的性能。 -
CMS 收集器(
Concurrent Mark Sweep Collector
)并发标记清除收集器, 适⽤于那些更愿意缩短垃圾回收暂停时间并且负担的起与垃圾回收共享处
理器资源的应⽤ -
G1 收集器(
Garbage-First Garbage Collector
)适⽤于⼤容量内存的多核服务器, 可以在满⾜垃圾回收暂停时间⽬标的同时, 以最⼤可能性实现
⾼吞吐量(JDK1.7
之后)垃圾回收器参数
参数 描述 -XX:+UseSerialGC 启⽤串⾏收集器 -XX:+UseParallelGC 启⽤并⾏垃圾收集器,配置了该选项,那么 -XX:+UseParallelOldGC默认启⽤ -XX:+UseParNewGC 年轻代采⽤并⾏收集器,如果设置了 -XX:+UseConcMarkSweepGC选项,⾃动启⽤ -XX:ParallelGCThreads 年轻代及⽼年代垃圾回收使⽤的线程数。默认值依赖于JVM使⽤的CPU个数 -XX:+UseConcMarkSweepGC 对于⽼年代,启⽤CMS垃圾收集器。 当并⾏收集器⽆法满⾜应⽤的延迟需求时,
推荐使⽤CMS或G1收集器。启⽤该选项后,-XX:+UseParNewGC⾃动启⽤。-XX:+UseG1GC 启⽤G1收集器。 G1是服务器类型的收集器, ⽤于多核、⼤内存的机器。
它在保持⾼吞吐量的情况下,⾼概率满⾜GC暂停时间的⽬标。在
bin/catalina.sh
的脚本中 , 追加如下配置:JAVA_OPTS="-XX:+UseConcMarkSweepGC"
如果存在虚拟机内存相关参数配置,则直接在
JAVA_OPTS
中追加即可。
6-3 Tomcat 配置调优
-
启用
Tomcat
线程池 -
调整
Tomcat
的连接器调整
Tomcat/conf/server.xml
中关于链接器的配置可以提升应⽤服务器的性能。参数 说明 maxConnections 最⼤连接数,当到达该值后,服务器接收但不会处理更多的请求, 额外的请求将会阻塞直到连接数低于maxConnections 。可通过ulimit -a 查看服务器限制。对于CPU要求更⾼(计算密集型)时,建议不要配置过⼤ ; 对于CPU要求不是特别⾼时,建议配置在2000左右(受服务器性能影响)。 当然这个需要服务器硬件的⽀持 maxThreads 最⼤线程数,需要根据服务器的硬件情况,进⾏⼀个合理的设置 acceptCount 最⼤排队等待数,当服务器接收的请求数量到达maxConnections ,此时Tomcat会将后⾯的请求,存放在任务队列中进⾏排序, acceptCount指的就是任务队列中排队等待的请求数 。 ⼀台Tomcat的最⼤的请求处理数量,是maxConnections+acceptCount -
禁⽤
AJP
连接器
-
调整
IO
模式Tomcat8
之前的版本默认使⽤BIO
(阻塞式IO
),对于每⼀个请求都要创建⼀个线程来处理,不适合⾼并发;Tomcat8
以后的版本默认使⽤NIO
模式(⾮阻塞式IO
)。
当Tomcat
并发性能有较⾼要求或者出现瓶颈时,我们可以尝试使⽤APR
模式,APR
(Apache Portable Runtime
)是从操作系统级别解决异步IO
问题,使⽤时需要在操作系统上安装APR
和Native
(因为APR
原理是使⽤使⽤JNI
技术调⽤操作系统底层的IO
接⼝)。 -
动静分离
可以使⽤
Nginx + Tomcat
相结合的部署⽅案,Nginx
负责静态资源访问,Tomcat
负责Jsp
等动态资
源访问处理(因为Tomcat
不擅⻓处理静态资源)。
源码
------------------------------------- 保持热爱,翻阅山海。 -------------------------------------