本文基于tomcat 8.5.50
1. Tomcat 系统架构与原理剖析
1.1 浏览器访问服务器流程
1.2 Tomcat 处理请求大致过程
- Http 服务器接收用户发送的请求,然后再交给 Servlet 容器处理,Servlet 容器再调用具体的业务处理类
- Servlet 容器避免了 Tomcat 与具体的业务处理类耦合
1.3 Tomcat Servlet 容器处理流程
- Http 服务器接收到 Request 请求后,用 ServletRequest 对象封装请求信息
- 然后进一步调用 Servlet 容器某个具体的 Servlet
- Servlet 容器通过 url 和 Servlet 的映射关系定位到具体的 Servlet
- 如果 Servlet 没有被加载,那么先用反射机制创建这个 Servlet 并调用 init 方法完成初始化
- 继续调用这个 Servlet 的 Service 方法,用 ServletResponse 对象封装处理结果
- 把 ServletResponse 对象返回给 Http 服务器,Http 服务器再把 Response 返回给客户端
1.4 Tomcat 总体架构
Tomcat 设计了两个核心组件来完成两大核心功能
- 连接器(Connector):负责对外交流,处理 Socket 连接,负责网络字节流与 Request 和 Response 对象的转换
- 容器(Container):负责内部处理,加载和管理 Servlet ,处理具体的 Request 请求
1.5 Tomcat 连接器 - Coyote
Coyote 将 Socket 输入转换封装为 Request 对象,进一步封装为 ServletRequest 对象交给 Catalina 容器处理,处理请求完成后,Catalina 通过 Coyote 提供的 Response 对象将结果写入输出流。Coyote 避免了 Catalina 与具体的请求协议和 IO 操作方式耦合。
Coyote 内部组件及处理流程
-
Endpoint
Endpoint 是 Coyote 的通信端点,用来实现 TCP / IP 协议,是具体 Socket 接收和发送处理器
-
Processor
Processor 是 Coyote 的协议处理接口,用来实现 HTTP 协议,接收来自 Endpoint 的 Socket,读取字节流解析成 Tomcat Request 和 Response 对象,并通过 Adapter 将请求提交到容器处理
-
ProtocolHanlder
ProtocolHanlder 是 Coyote 的协议接口,通过 Endpoint 和 Processor 实现对具体协议的处理能力,Tomcat 根据应用层协议和传输层I/O模型的组合提供了6个实现类
-
Adapter
CoyoteAdatpter 负责将 Tomcat Request 转换成标准的 ServletRequest,再调用容器
1.6 Tomcat 容器 - Catalina
Catalina 是 Tomcat 的核心,其他模块是通过 conf/server.xml
引入的,Catalina 结构如下
-
Catalina
负责解析 Tomcat 的配置文件(server.xml),以此来创建服务器Server组件并进行管理。
-
Server
服务器表示整个 Catalina Servlet 容器以及其他组件,负责组装并启动 Servlet 引擎和 Tomcat 连接器。Server 通过实现 Lifecycle 接口,提供了一种优雅的启动和关闭整个系统的方式。
-
Service
服务是 Server 内部组件,一个 Server 包含多个 Service。Service 将多个 Connector 绑定到一个 Container。
-
Container
容器负责处理用户的servlet请求,并返回对象给web用户的模块。Container 组件下包含了 Engine、Host、Context 和 Wrapper 4种组件,组件之间是父子关系。
-
Engine
表示整个 Catalina 的 Servlet 引擎,用来管理多个虚拟站点。一个 Service 最多只能有一个Engine,一个 Engine 可以包含多个 Host。
-
Host
代表一个虚拟主机(站点),可以包含多个 Context。
-
Context
表示一个 Web 应用程序,可以包含多个 Wrapper。
-
Wrapper
Wrapper 表示一个 Servlet,是容器的最底层,没有子容器。
2. Tomcat 服务器核心配置
2.1 Server 标签
<!--
Server:根元素,创建一个 Server 实例
属性:
port:关闭服务器的监听端口
shutdouwn:关闭服务器的指定字符串
子标签:
Listener:监听器
GlobalNamingResources:全局资源
Service:定义一个Service服务
-->
<Server port="8005" shutdown="SHUTDOWN">
<!-- 以日志形式输出服务器、操作系统、JVM的版本信息 -->
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<!-- 安全校验监听器 -->
<Listener className="org.apache.catalina.security.SecurityListener" />
<!-- 加载(服务器启动)和销毁(服务器关闭)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 池的线程,以避免TreadLocal相关的内存泄漏 -->
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<!-- GlobalNamingResources定义了全名命名服务 -->
<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可以包含一或多个Connectors,共用一个Container -->
<Service name="Catalina">
...
</Service>
</Server>
2.2 Service 标签
<!--
Service:创建 Service 实例,默认使⽤ org.apache.catalina.core.StandardService
属性:
name:指定Service的名称,默认为Catalina
子标签:
Listener:为Service添加生命周期监听器
Executor:配置Service共享线程池
Connector:配置Service包含连接器
Engine:配置Service中连接器对应的Servlet容器引擎
-->
<Service name="Catalina">
<!--可以定义一或多个共享线程池 Executor 给 Connector 使用-->
<!--
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
-->
<!-- Connector 标签通过属性 executor 使用具体的线程池-->
<!--
<Connector executor="tomcatThreadPool"
port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
-->
<!-- 监听8080端口,支持HTTP/1.1协议,8443是加密端口 -->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<!-- 监听8009端口,支持AJP/1.3协议 -->
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
...
</Engine>
</Service>
2.3 Executor 标签
<!--
Executor属性:
name:线程池名称,⽤于 Connector 中指定
namePrefix:所创建的每个线程的名称前缀,⼀个单独的线程名称为namePrefix+threadNumber
maxThreads:池中最⼤线程数
minSpareThreads:活跃线程数,也就是核⼼池线程数,这些线程不会被销毁,会⼀直存在
maxIdleTime:线程空闲时间,超过该时间后,空闲线程会被销毁,默认值为60000(1分钟)
maxQueueSize:在被执⾏前最⼤线程排队数⽬,默认为Int的最⼤值,也就是⼴义的⽆限。除⾮特殊情况,这个值 不需要更改,否则会有请求不会被处理的情况发⽣
prestartminSpareThreads:启动线程池时是否启动 minSpareThreads部分线程。默认值为false
threadPriority:线程池中线程优先级,默认值为5,值从1到10
className:线程池实现类,未指定情况下,默认实现类为org.apache.catalina.core.StandardThreadExecutor。如果想使⽤⾃定义线程池⾸先需要实现
org.apache.catalina.Executor接⼝
-->
<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"/>
2.4 Connector 标签
<!--
Connector:⽤于创建链接器实例,默认情况下,server.xml 配置了两个链接器,⼀个⽀持HTTP协议,⼀个⽀持AJP协议
属性:
port:端⼝号,Connector ⽤于创建服务端 Socket 并进⾏监听,以等待客户端请求链接。
如果该属性设置为0, Tomcat将会随机选择⼀个可⽤的端⼝号给当前 Connector 使⽤
protocol:当前Connector ⽀持的访问协议。 默认为 HTTP/1.1 ,并采⽤⾃动切换机制选择⼀个基于 JAVA NIO 的链接器或者基于本地APR的链接器(根据本地是否含有Tomcat的本地库判定)
connectionTimeOut:Connector 接收链接后的等待超时时间,单位为 毫秒。 -1 表示不超时。
redirectPort:当前 Connector不⽀持SSL请求,接收到了⼀个请求,并且也符合 security-constraint 约束,需要SSL传输,Catalina⾃动将请求重定向到指定的端⼝。
executor:指定共享线程池的名称,也可以通过 maxThreads、minSpareThreads 等属性配置内部线程池。但如果已经指定了 executor,那么已自定义线程池的参数为准。
URIEncoding:⽤于指定编码URI的字符编码, Tomcat8.x版本默认的编码为 UTF-8 , Tomcat7.x版本默认为ISO-8859-1
-->
<!--org.apache.coyote.http11.Http11NioProtocol , ⾮阻塞式 Java NIO 链接器-->
<Connector port="8080"
protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<Connector port="8080"
protocol="HTTP/1.1"
executor="commonThreadPool"
maxThreads="1000"
minSpareThreads="100"
acceptCount="1000"
maxConnections="1000"
connectionTimeout="20000"
compression="on"
compressionMinSize="2048"
disableUploadTimeout="true"
redirectPort="8443"
URIEncoding="UTF-8" />
2.5 Engine、Host、Context 标签
<!--
Engine:表示 Servlet 引擎
属性:
name: ⽤于指定 Engine 的名称,默认为Catalina
defaultHost:默认使⽤的虚拟主机名称,当客户端请求指向的主机⽆效时,将交由默认的虚拟主机处
理, 默认为localhost
-->
<Engine name="Catalina" defaultHost="localhost">
<!--
Host:配置一个虚拟主机(可以配置多个站点)
属性:
name:域名
appBase:Web 应用程序组的路径(有多个app)
unpackWARs:自动展开 war 压缩包再运行 Web 应用
autoDeploy:自动部署,检测 appBase 目录下文件变化时自动应用到正在运行的 Web 应用
-->
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
<!-- Context:配置一个 Web 应用
docBase:Web应⽤⽬录或者War包的部署路径。可以是绝对路径或者是相对于Host appBase的相对路径。
path:Web应⽤的 Context 路径。
如果我们Host名为localhost,则该web应⽤访问的根路径为:http://localhost:8080/hello
-->
<Context docBase="/project_path" path="/hello"></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>
</Engine>
3. 手写 mini Tomcat
1)提供服务,接收请求(Socket通信)
2)请求信息封装成Request对象(Response对象)
3)客户端请求资源,资源分为静态资源(html)和动态资源(Servlet)
4)资源返回给客户端浏览器
4. Tomcat 核心流程
管理生命周期的接口类 Lifecycle
4.1 启动流程
通过 startup.sh
调用 catalina.sh
脚本,再调用 Bootstrap
的 main
方法启动
查看 main 方法,有3行代码需要关注
- bootstrap.init(); // 初始化,主要是创建一个 Catalina 实例
- daemon.load(args); // 初始化一系列子容器
- daemon.start(); // 启动一系列子容器
从 daemon.load(args) 开始初始化容器到绑定 0.0.0.0:8080 的堆栈
从daemon.start() 开始启动一系列子容器到创建一个Acceptor线程的堆栈
4.2 请求流程
- 大致流程
- 详细流程
- Mapper组件
部署到tomcat 跟踪调用
- 新建一个web项目web-demo,配置servlet
- 编译后将out文件夹下面的web-demo拷贝到源码项目的source/webapps下
- 启动源码项目,访问http://localhost:8080/web-app/hello
- 在NioEndpoint 的 startAcceptorThreads 方法上面的Poller线程类打断点
- org.apache.tomcat.util.net.NioEndpoint.Poller#processKey
5. Tomcat 类加载机制
5.1 JVM 类加载机制
类加载器 | 作用 |
---|---|
引导启动类加载器 BootstrapClassLoader | c++编写,加载java核⼼库 java.*,⽐如rt.jar中的类,构造ExtClassLoader和AppClassLoader |
扩展类加载器 ExtClassLoader | java编写,加载扩展库 JAVA_HOME/lib/ext⽬录下的jar中的类,如classpath中的jre ,javax.*或者java.ext.dir指定位置中的类 |
系统类加载器 SystemClassLoader AppClassLoader | 默认的类加载器,搜索环境变量 classpath 中指明的路径 |
当 JVM 运⾏过程中,⽤户⾃定义了类加载器去加载某些类时,会按照下⾯的步骤(⽗类委托机制)
- ⽤户⾃⼰的类加载器,把加载请求传给⽗加载器,⽗加载器再传给其⽗加载器,⼀直到加载器
树的顶层 - 最顶层的类加载器⾸先针对其特定的位置加载,如果加载不到就转交给⼦类
- 如果⼀直到底层的类加载都没有加载到,那么就会抛出异常 ClassNotFoundExceptio
5.2 双亲委派机制
- 含义:当某个类加载器需要加载某个.class⽂件时,它⾸先把这个任务委托给他的上级类加载器,递归这个操
作,如果上级的类加载器没有加载,⾃⼰才会去加载这个类。(先向上找父加载器,然后自顶向下尝试加载) - 作用:
- 防止重复加载class,委托过程中只要加载了就不会访问下一个加载器
- 保证核心 class 不能被篡改(委托不会篡改 class,即使篡改也不会加载,即使加载也不是同一个 class)
5.3 Tomcat 类加载机制
- 引导类加载器 和 扩展类加载器 的作⽤不变
- 系统类加载器正常情况下加载的是 CLASSPATH 下的类,但是 Tomcat 的启动脚本并未使⽤该变量,⽽是加载tomcat启动的类,⽐如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定。位于CATALINA_HOME/bin下
- Common 通⽤类加载器加载Tomcat使⽤以及应⽤通⽤的⼀些类,位于CATALINA_HOME/lib下,⽐如servlet-api.jar
- Catalina ClassLoader ⽤于加载服务器内部可⻅类,这些类应⽤程序不能访问
- Shared ClassLoader ⽤于加载应⽤程序共享类,这些类服务器不会依赖
- Webapp ClassLoader,每个应⽤程序都会有⼀个独⼀⽆⼆的Webapp ClassLoader,他⽤来加载本应⽤程序 /WEB-INF/classes 和 /WEB-INF/lib 下的类。
tomcat 8.5 默认改变了严格的双亲委派机制
- ⾸先从 Bootstrap Classloader加载指定的类
- 如果未加载到,则从 /WEB-INF/classes加载
- 如果未加载到,则从 /WEB-INF/lib/*.jar 加载
- 如果未加载到,则依次从 System、Common、Shared 加载(在这最后⼀步,遵从双亲委派机制
6. Tomcat 对 HTTPS 的支持
6.1 HTTPS 与 HTTP 的主要区别
- 需要证书:使用 https 需要到 CA 申请 SSL 证书
- 默认端口不同:https 默认使用 8443 端口,而 http 默认使用 8080 端口
- 数据加密:https 是具有 SSL 加密的安全传输协议,对数据的传输进行加密,相当于 http 的升级版
- 可认证:https 是由 http + SSL协议构建的可进行加密传输和身份认证的网络协议,而http 是无状态不安全的
6.2 HTTPS 工作原理
6.3 Tomcat 配置 HTTPS
-
使用 JDK 的 keytool 生成免费的秘钥库文件(证书)
keytool -genkey -alias lagou -keyalg RSA -keystore lagou.keystore
-
在 conf/server.xml 添加 SSLHostConfig
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" maxThreads="150" schema="https" secure="true" SSLEnabled="true"> <SSLHostConfig> <Certificate certificateKeystoreFile="${TOMCAT_HOME}/conf/lagou.keystore" certificateKeystorePassword="yourpassword" type="RSA"/> </SSLHostConfig> </Connector>
-
使⽤ https 协议访问 8443 端⼝(https://localhost:8443)
7. Tomcat 性能优化策略
系统性能指标:
-
响应时间 TR:执行某个操作的耗时
-
吞吐量 TPS:系统在给定时间内能够支持的事务数量
7.1 JVM 虚拟机优化
-
JVM 内存参数
参数 作用 建议 -server 启动Server,以服务端模式运行 建议开启服务端模式 -Xms 最小堆内存 建议与-Xmx相同 -Xmx 最大堆内存 建议设置为可用内存的80% -XX:MetaspaceSize 元空间初始值 -XX:MaxMetaspaceSize 元空间最大初始值 默认无限 -XX:NewRatio 年轻代和老年代大小比值,取整,默认为2 不需要修改 -XX:SurvivorRatio Eden区和Servivor去大小比值,取整,默认为8 不需要修改 示例
JAVA_OPTS="-server -Xms2048m -Xmx2048m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"
使用内存映射工具查看 Tomcat 中 JVM 的内存配置:jhsdb jmap --heap --pid xxxx
-
垃圾回收策略
垃圾回收性能指标:
- 吞吐量:工作时间(排除GC时间)占总时间的百分比
- 暂停时间:由垃圾回收导致的应用程序停止响应次数/时间
垃圾收集器
收集器 说明 串行回收器 Serial Collector 单线程执行垃圾回收工作,回收时工作进程会暂停 并行回收器 Parallel Collector 多线程执行垃圾回收工作,回收时工作进程任然暂停 并发标记清除收集器 CMS 适用于愿意缩短垃圾回收暂停时间并且负担得起与垃圾回收共享处理器资源的应用(CPU要好一些) G1 收集器 适用于大容量内存的多核服务器,满足GC停顿时间要求的同时,还具备高吞吐量 垃圾回收器参数
参数 作用 -XX:+UserConcMarkSweepGC 启动CMS垃圾回收器,启用后,-XX:+UseParNewGC自动启用 -XX:+UserG1GC 启动G1垃圾回收器 -
通过
${JAVA_HOME}/bin/jconsole.exe
查看 JVM 信息
7.2 Tomcat 自身配置的优化
- 使用线程池:在 Connector 标签中通过 executor 属性使用公共的线程池
- 修改 Connector 连接参数:maxConnections、maxThreads、acceptCount
- 禁用AJP:注释默认的支持AJP的 Connector
- 调整 IO 模式:Tomcat 8之前的版本默认使用BIO,可以修改 protocol 为 Http11NioProtocol
- 动静分离:Tomcat 处理动态资源,Nginx 处理静态资源