一、Tomcat简介
1、Tomcat 是什么?
Apache Tomcat(通常简称为 Tomcat)是一个开源的 Web 服务器 和 Servlet 容器,由 Apache 软件基金会维护。它实现了 Java Servlet、JavaServer Pages (JSP) 和 WebSocket 等规范,是部署和运行 Java Web 应用程序的核心工具之一。
Tomcat 核心功能:
- 处理 Socket 连接,负责网络字节流与 Request 和 Response 对象的转化。(连接器(Connector))
- 加载和管理 Servlet,以及具体处理 Request 请求。(容器(Container))
Tomcat 设计两个核心组件连接器(Connector)和容器(Container)来分别做这两件事情。连接器负责对外交流,容器负责内部处理。
2、Tomcat 架构
Tomcat 8.5
Server (Tomcat 实例)
│
└── Service (如 "Catalina")
│
├── **Connector** (处理外部协议,如 HTTP/AJP)
│ │
│ ├── ProtocolHandler (协议实现,如 Http11NioProtocol)
│ │ │
│ │ ├── **Endpoint** (网络 I/O 处理)
│ │ │ ├── Acceptor (接收新连接)
│ │ │ ├── Handler (处理 Socket 读写)
│ │ │ └── AsyncTimeout (管理异步超时)
│ │ │
│ │ ├── **Processor** (解析请求,生成 Tomcat Request 对象)
│ │ └── **Adapter** (将请求适配为 Servlet 规范的 Request/Response)
│ │
│ └── Executor (线程池,处理请求)
│
└── **Engine** (请求处理核心容器)
│
└── **Host** (虚拟主机,如 "localhost")
│
└── **Context** (Web 应用,如 "/myapp")
│
├── Wrapper (Servlet 定义)
├── Valve (拦截器,如 AccessLogValve)
└── Resources (静态资源)
- Server
- 职责:代表整个 Tomcat 实例,管理其生命周期(启动/停止)。
- 配置:对应 server.xml 中的 元素。
- 子组件:包含多个 Service,每个 Service 提供独立的功能集。
- Service
- 职责:将 Connector 与 Container 组合,提供完整的请求处理服务。
- 配置: 元素包含一个 Container 和多个 Connector。
- 示例:默认服务 Catalina 处理 HTTP 请求。
- Connector
- 职责:处理网络连接,解析协议(如 HTTP/1.1、HTTP/2、AJP),将请求转发至 Container。
- 类型:
- HTTP Connector:处理浏览器请求(默认端口 8080)。
- AJP Connector:与 Web 服务器(如 Apache)集成。
- 实现:支持 BIO(阻塞)、NIO(非阻塞)、APR(本地库)等模式。
- 流程:接收请求 → 生成 ServletRequest → 调用 Container 处理 → 返回响应。
- 核心子组件:
- Endpoint:负责网络通信(如 NioEndpoint)。
- Processor:解析协议(如 HTTP 报文)。
- Adapter:将原始请求转换为 ServletRequest,交给 Engine。
- Container
- 层级结构:包含 Engine、Host、Context、Wrapper,按层次处理请求。
- 核心子组件:
- Engine
- 职责:Servlet 引擎,管理多个虚拟主机(Host)。
- 配置: 元素,通常对应一个服务域名或默认主机。
- Host
- 职责:虚拟主机,基于域名分发请求(如 localhost、example.com)。
- 配置: 元素,包含多个 Context(Web 应用)。
- Context
- 职责:代表一个 Web 应用(如 WAR 包),管理 Servlet 和静态资源。
- 配置: 元素,指定应用路径(如 /app)和资源目录。
- 特性:独立的 ClassLoader,实现应用隔离。
- Engine
- Wrapper
- 职责:封装单个 Servlet,处理其生命周期和请求。
- 配置: 定义在 web.xml 或注解中。
- Pipeline 与 Valve
- Pipeline:每个容器(如 Engine、Host)拥有一个处理链。
- Valve:链中的处理单元,实现日志、权限检查等功能。
- 基础 Valve:链末端的组件,负责调用下一层容器(如 Engine → Host)。
- 自定义 Valve:如 AccessLogValve 记录访问日志。
- 流程:请求依次通过各层 Valve,最终由基础 Valve 传递至下层容器。
- 其他关键组件
- Loader:为 Context 提供类加载机制(WebappClassLoader),优先加载应用内 JAR。
- Manager:会话管理,支持内存、持久化、集群会话存储。
- Realm:安全认证,集成数据库、LDAP 等数据源。
- Executor:共享线程池,优化 Connector 的请求处理效率。
3、Tomcat 请求处理流程
客户端 → Endpoint(接收连接) → Processor(协议解析) → CoyoteAdapter(转换请求)
→ Engine → Host → Context → Wrapper(Servlet处理)
→ 反向逐层返回 → Processor写响应 → Endpoint发送数据 → 客户端
- 接收连接
Endpoint
的Acceptor
接收新连接,Handler
处理 I/O 事件。- NIO 模式下使用
Selector
实现非阻塞读写。
- 协议解析
Processor
解析 HTTP 头、URI、参数等,生成Request
对象。
- 适配与传递
Adapter
将Request
转换为ServletRequest
,调用Engine
的管道(Pipeline)。
- 路由与处理
Engine
→Host
→Context
→Wrapper
逐级路由。- 各层
Valve
链执行预处理(如日志记录)。
- Servlet 执行
Wrapper
调用Servlet.service()
,处理业务逻辑。
- 响应返回
- 响应逆序通过各层 Valve,最终由 Connector 返回客户端。
4、Tomcat 启动流程
- 启动Tomcat:
- 在Windows系统中,需要调用
bin/startup.bat
脚本来启动Tomcat。 - 在Linux系统中,则需要调用
bin/startup.sh
脚本来启动。
- 在Windows系统中,需要调用
- 调用catalina.bat/catalina.sh:
- 在
startup.bat
或startup.sh
脚本中,会调用catalina.bat
(Windows)或catalina.sh
(Linux)脚本。 - 这个脚本是Tomcat启动的核心部分,它负责调用Bootstrap类的main方法。
- 在
- 调用Bootstrap的main方法:
- 在
catalina.bat
或catalina.sh
脚本中,最终会调用org.apache.catalina.startup.Bootstrap
类的main
方法。 - 这个方法是Tomcat启动的入口点。
- 在
- 初始化(init方法):
- 在Bootstrap的
main
方法中,首先会调用init
方法来创建Catalina对象,并初始化类加载器。 - Tomcat会默认初始化三种特有的类加载器:commonLoader、catalinaLoader和sharedLoader。但在没有个性化配置的情况下,catalinaLoader和sharedLoader实际上都指向commonLoader。
- 在Bootstrap的
- 加载(load方法):
- 接着,Bootstrap的
main
方法会调用load
方法。 - 在
load
方法中,会进一步调用Catalina的load
方法。 - Catalina的
load
方法负责进行初始化工作,包括构造Digester对象来解析XML配置文件(如server.xml和web.xml)。
- 接着,Bootstrap的
- 组件初始化:
- 在解析完配置文件后,Tomcat会依次初始化各个组件,包括Server、Service、Connector、Engine、Host和Context。
- 这些组件是Tomcat运行的重要组成部分,它们按照层级关系进行初始化。
- 启动服务:
- 当所有组件初始化完成后,Tomcat会依次启动Connector、Engine、Host和Context。
- Context启动:
- 加载类与资源:通过
WebappClassLoader
加载WEB-INF/classes
和WEB-INF/lib
。 - 初始化Servlet:调用
ServletContainerInitializer
和ServletContextListener
。 - 构建Filter链与Servlet映射:基于
web.xml
或注解配置。
- 加载类与资源:通过
- Context启动:
- 最终启动整个Tomcat服务,并监听对应的端口号(如默认的8080端口),准备接受客户端请求。
- 部署应用:
- 启动完成后,Tomcat会扫描指定的Web应用程序目录。
- 自动部署已经打包好的Web应用程序,使其能够处理客户端的请求。
- 当所有组件初始化完成后,Tomcat会依次启动Connector、Engine、Host和Context。
总结来说,Tomcat的启动流程是一个从调用启动脚本开始,经过初始化类加载器、解析配置文件、初始化组件、启动服务到最终部署应用的复杂过程。每一步都紧密相连,确保Tomcat能够正确启动并运行。
5、Servlet生命周期
Servlet 类实例创建:
- 当容器启动时,会读取在 webapps 目录下所有的 web 应用中的 web.xml 文件,然后对 xml 文件进行解析,并读取 servlet 注册信息。然后,将每个应用中注册的 servlet 类都进行加载,并通 过反射的方式实例化。(有时候也是在第一次请求时实例化)
- 在 servlet 注册时加上
<load-on-startup>1</load-on-startup>
:如果为正数,则在一开始就实例化,如果不写或为负数,则第一次请求实例化。
6、Pipeline-Value
Tomcat 的 Pipeline(管道) 和 Valve(阀门) 是实现责任链模式的核心机制,用于协调容器(如 Engine、Host、Context、Wrapper)内部的多级处理逻辑。这种设计将请求处理流程分解为多个独立的步骤(Valve),通过链式调用实现高内聚、低耦合的协作。
Tomcat 的 Pipeline-Valve 机制 通过责任链模式,将请求处理流程分解为多个可插拔的模块(Valve),实现了容器层级的逻辑解耦和灵活扩展。每个层级的 Pipeline 负责协调本层的 Valve,最终通过基础 Valve 将请求路由到下一层容器,是 Tomcat 处理高并发请求的核心设计之一。
(1)Pipeline-Value是什么?
- Pipeline
- 定义:每个容器(Engine/Host/Context/Wrapper)内部都有一个 Pipeline,用于管理该层级的处理逻辑链。
- 作用:Pipeline 维护一组 Valve,按顺序执行这些 Valve,最终调用 基础 Valve(StandardXXXValve) 将请求传递到下一层容器。
- Valve(阀门)
- 定义:Valve 是处理逻辑的最小单元,每个 Valve 负责一项独立的功能(如日志记录、权限校验、路由转发)。
- 分类:
- 普通 Valve:用户自定义或 Tomcat 内置的扩展逻辑(如
AccessLogValve
)。 - 基础 Valve(StandardXXXValve):每个容器层级的默认末端 Valve,负责将请求传递到下一层容器(如
StandardEngineValve
将请求路由到 Host)。
- 普通 Valve:用户自定义或 Tomcat 内置的扩展逻辑(如
(2)责任链模式
-
链式调用:Pipeline 按顺序执行所有 Valve,每个 Valve 处理完逻辑后,必须调用
getNext().invoke()
触发下一个 Valve。 -
终止条件:基础 Valve 是责任链的末端,不再调用后续 Valve,而是将请求交给下层容器。
(3)Pipeline 和 Valve 的协作流程
以 Engine 层 为例,假设配置了多个 Valve 和一个基础 Valve StandardEngineValve
:
Pipeline: Valve1 → Valve2 → ... → StandardEngineValve
请求处理流程如下:
- Valve1 执行:完成自己的逻辑(如记录请求时间)。
- Valve1 调用下一个 Valve:通过
getNext().invoke(request, response)
触发 Valve2。 - Valve2 执行:完成自己的逻辑(如 IP 黑名单过滤)。
- 重复链式调用:直到最后一个 Valve(
StandardEngineValve
)。 - 基础 Valve 执行:
StandardEngineValve
根据域名找到对应的 Host,并将请求交给 Host 的 Pipeline 处理。
(4)Valve 的核心方法
所有 Valve 必须实现 org.apache.catalina.Valve
接口,核心方法是:
public void invoke(Request request, Response response, ValveContext context) {
// 1. 执行当前 Valve 的逻辑(如日志、权限校验)
doSomething(request, response);
// 2. 调用下一个 Valve
context.invokeNext(request, response);
}
(5)分层容器的 Pipeline 示例
- Engine 层 Pipeline
- Valve 示例:
AccessLogValve
:记录访问日志。RemoteAddrFilterValve
:过滤非法 IP。
- 基础 Valve:
StandardEngineValve
(负责路由到 Host)。
- Valve 示例:
- Host 层 Pipeline
- Valve 示例:
ErrorReportValve
:生成错误页面。
- 基础 Valve:
StandardHostValve
(负责路由到 Context)。
- Valve 示例:
- Context 层 Pipeline
- Valve 示例:
WebResourceAccessValve
:静态资源访问控制。
- 基础 Valve:
StandardContextValve
(负责路由到 Wrapper,并触发 FilterChain)。
- Valve 示例:
- Wrapper 层 Pipeline
- Valve 示例:
- 通常无自定义 Valve(逻辑由 Servlet 和 Filter 处理)。
- 基础 Valve:
StandardWrapperValve
(负责调用 Servlet 的service()
方法)。
- Valve 示例:
(6)配置 Valve 的示例
在 server.xml
中为 Engine 添加一个日志 Valve:
<Engine name="Catalina" defaultHost="localhost">
<Valve className="org.apache.catalina.valves.AccessLogValve"
directory="logs"
prefix="engine_access_log"
suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
<Host name="localhost" appBase="webapps" ... />
</Engine>
(7)Valve 与 Filter 的区别
特性 | Valve | Filter(Servlet 规范) |
---|---|---|
作用范围 | Tomcat 容器层级(Engine/Host 等) | Web 应用层级(Servlet 和 URL 映射) |
配置位置 | Tomcat 的 server.xml | 应用的 web.xml 或注解 |
执行阶段 | 容器层级的全局处理(如路由、日志) | 应用层级的请求处理(如权限、编码) |
依赖关系 | 紧耦合于 Tomcat 容器 | 符合 Servlet 规范,与容器解耦 |
(8)Pipeline-Valve 的设计优势
- 模块化:每个 Valve 只关注单一功能,便于扩展和维护。
- 灵活性:可通过配置动态添加、删除或调整 Valve 顺序。
- 统一性:所有容器层级使用相同的处理机制,降低复杂度。
- 性能优化:责任链模式避免多层嵌套调用,逻辑清晰高效。
(9)完整请求处理流程中的 Pipeline 协作
客户端请求 → Connector → Engine Pipeline(Valve1 → Valve2 → StandardEngineValve)
→ Host Pipeline(ValveA → StandardHostValve)
→ Context Pipeline(ValveX → StandardContextValve)
→ Wrapper Pipeline(StandardWrapperValve → Servlet.service())
→ 反向逐层返回响应
7、Tomcat Connector工作模式
工作模式对比:
模式 | I/O 模型 | 线程策略 | 适用场景 | 性能 | 配置复杂度 |
---|---|---|---|---|---|
BIO | 阻塞式 | 一请求一线程 | 低并发、简单应用 | 低 | 简单 |
NIO | 非阻塞式 | 多路复用 | 高并发、常规 Web 应用 | 高 | 中等 |
NIO2 | 异步 I/O | 回调驱动 | 长连接、WebSocket | 中高 | 中等 |
APR | 本地库非阻塞 | 多路复用 | 高吞吐、生产环境 | 最高 | 复杂 |
AJP | 二进制协议 | 代理转发 | 与前端服务器集成 | 高 | 中等 |
如何选择工作模式?
- 常规场景:默认使用 NIO 模式(Tomcat 8.5+ 已默认启用)。
- 高并发生产环境:优先选择 APR 模式(需安装依赖库)。
- 代理集成:使用 AJP 模式与 Apache/Nginx 配合。
- 长连接服务:尝试 NIO2 模式(如 WebSocket)。
8、Tomcat 的典型使用场景
- 部署 Java Web 应用
- 运行基于 Servlet/JSP 的 Web 项目(如 Spring MVC、Struts)。
- 支持 WAR 包部署:将项目打包为
app.war
,放入webapps
目录即可自动解压运行。
- 开发测试环境
- 开发者本地调试时常用 Tomcat,启动速度快,配置简单。
- 轻量级生产环境
- 适合中小型应用,对资源消耗低,易于维护。
二、Tomcat安装
1、下载
2、安装
- 将下载的 .zip 压缩包 , 解压到系统的目录(建议是没有中文不带空格的目录)下即可。
3、启动停止
- 启动:双击 bin/startup.bat 文件
- 停止:双击 bin/shutdown.bat 文件
- 访问:http://localhost:8080
4、Tomcat 目录结构(8.5)
apache-tomcat-8.5.x/
├── bin/ # 核心脚本(启动、停止、配置等)
├── conf/ # 配置文件(server.xml、web.xml等)
├── lib/ # 依赖库(Servlet/JSP API等)
├── logs/ # 日志文件(catalina.out、访问日志等)
├── webapps/ # 部署的Web应用(WAR包或目录)
├── work/ # 临时文件(JSP编译后的Servlet类)
├── temp/ # 运行时临时文件
└── ROOT/ # 默认根应用(对应http://localhost:8080)
4.1 bin
- 核心管理脚本
脚本名称 | 作用 | 适用系统 |
---|---|---|
startup.sh | 启动Tomcat(内部调用 catalina.sh start ) | Linux/Mac |
startup.bat | Windows下的启动脚本 | Windows |
shutdown.sh | 停止Tomcat(内部调用 catalina.sh stop ) | Linux/Mac |
shutdown.bat | Windows下的停止脚本 | Windows |
catalina.sh | 核心脚本,控制Tomcat生命周期(start/stop/run/debug等) | Linux/Mac |
catalina.bat | Windows版核心脚本 | Windows |
- 工具脚本
脚本名称 | 作用 |
---|---|
version.sh | 检查Tomcat和JVM版本信息 |
configtest.sh | 验证 conf/server.xml 的语法是否正确 |
setenv.sh | 自定义环境变量(如JVM参数、内存配置),启动时自动加载(需手动创建) |
digest.sh | 生成密码的MD5/SHA摘要(用于 conf/tomcat-users.xml 加密密码) |
tool-wrapper.sh | 通用工具执行脚本(如调用Java命令) |
- Windows特有脚本
脚本名称 | 作用 |
---|---|
service.bat | 将Tomcat安装为Windows服务(需管理员权限) |
tomcatXw.exe | GUI管理工具(配置服务、启动参数等) |
4.2 conf
server.xml
:主配置文件,定义 Tomcat 服务器全局行为,如虚拟主机(Host)、上下文路径(Context)等。web.xml
:全局 Servlet 配置,所有 Web 应用的默认 Servlet、Filter、MIME 类型等设置。单个应用的WEB-INF/web.xml
会覆盖此配置。context.xml
:全局上下文配置:定义所有 Web 应用的共享配置(如数据源、JNDI 资源)。单个应用可通过META-INF/context.xml
覆盖。tomcat-users.xml
:用户权限配置:定义访问 Tomcat 管理界面(如 Manager 和 Host Manager)的用户角色和密码。logging.properties
:日志配置:控制 Tomcat 日志的输出格式、级别和存储路径(如 catalina.out、访问日志)。catalina.policy
:安全策略文件:启用 SecurityManager 时,定义 Tomcat 的权限规则(如文件访问、网络权限)。catalina.properties
:类加载与安全配置:配置类加载器行为、禁止访问的包列表、安全防护参数等。jaspic-providers.xml
:认证提供者配置:定义 JASPIC(Java 认证服务提供接口)的认证模块。ROOT.xml
:默认根应用(ROOT)的上下文配置(若存在,覆盖 context.xml 中的全局设置)。<appname>.xml
:部署在 webapps 下的某个应用(如 myapp.xml)的独立上下文配置。global-web.xml
:已弃用:旧版本中替代 web.xml 的全局配置,Tomcat 8.5 中直接使用 web.xml。
4.3 logs
catalina.out
:启动日志和标准输出localhost_access_log.*.txt
:HTTP访问日志
4.4 webapps
- 存放WAR包或应用目录(如
ROOT.war
部署后为根应用) manager
:Tomcat管理界面(需配置用户权限)
5、Tomcat配置
5.1 server.xml
- server 标签
port:指定一个端口,这个端口负责监听关闭tomcat的请求。
shutdown:指定向端口发送的命令字符串。
- service标签
name:指定service的名字。
- Connector标签:表示客户端和service之间的连接
port:指定服务器端要创建的端口号,并在这个端口监听来自客户端的请求。
minProcessors:服务器启动时创建的处理请求的线程数。
maxProcessors:最大可以创建的处理请求的线程数。
enableLookups:如果为true,则可以通过调用request.getRemoteHost()进行DNS查询来得到远程客户端的实际主机名,若为false则不进行DNS查询,而是返回其ip地址。
redirectPort:指定服务器正在处理http请求时收到了一个SSL传输请求后重定向的端口号。
acceptCount:指定当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理。
connectionTimeout:指定超时的时间数(以毫秒为单位)。
- Engine标签:表示指定service中的请求处理机,接收和处理来自Connector的请求
defaultHost:指定缺省的处理请求的主机名,它至少与其中的一个host元素的name属性值是一样的。
- Context标签:表示一个web应用程序,通常为WAR文件,关于WAR的具体信息见servlet规范
docBase:该web应用的文档基准目录(Document Base,也称为Context Root),或者是WAR文件的路径。可以使用绝对路径,也可以使用相对于context所属的Host的appBase路径。
path:表示此web应用程序的url的前缀,这样请求的url为http://localhost:8080/path/****。
reloadable:这个属性非常重要,如果为true,则tomcat会自动检测应用程序的/WEB-INF/lib和/WEB-INF/classes目录的变化,自动装载新的应用程序,我们可以在不重起tomcat的情况下改变应用程序。
useNaming:如果希望Catalina为该web应用使能一个JNDI InitialContext对象,设为true。该InitialialContext符合J2EE平台的约定,缺省值为true。
workDir:Context提供的临时目录的路径,用于servlet的临时读/写。利用javax.servlet.context.tempdir属性,servlet可以访问该目录。如果没有指定,使用$CATALINA_HOME/work下一个合适的目录。
swallowOutput:如果该值为true,System.out和System.err的输出被重定向到web应用的logger。如果没有指定,缺省值为false
debug:与这个Engine关联的Logger记录的调试信息的详细程度。数字越大,输出越详细。如果没有指定,缺省为0。
- host标签:表示一个虚拟主机
name:指定主机名。
appBase:应用程序基本目录,即存放应用程序的目录。
unpackWARs:如果为true,则tomcat会自动将WAR文件解压,否则不解压,直接从WAR文件中运行应用程序。
- Logger标签:表示日志,调试和错误信息
className:指定logger使用的类名,此类必须实现org.apache.catalina.Logger接口。
prefix:指定log文件的前缀。
suffix:指定log文件的后缀。
timestamp:如果为true,则log文件名中要加入时间,如下例:localhost_log.2001-10-04.txt。
- Realm(表示存放用户名,密码及role的数据库)标签
className:指定Realm使用的类名,此类必须实现org.apache.catalina.Realm接口。
- Valve标签:功能与Logger差不多,其prefix和suffix属性解释和Logger 中的一样
className:指定Valve使用的类名,如用org.apache.catalina.valves.AccessLogValve类可以记录应用程序的访问信息。
directory:指定log文件存放的位置。
pattern:有两个值,common方式记录远程主机名或ip地址,用户名,日期,第一行请求的字符串,HTTP响应代码,发送的字节数。combined方式比common方式记录的值更多。
5.2 配置虚拟目录
- 直接部署到webapps目录下面访问。
- 修改conf/server.xml文件。在<Host name="localhost" appBase="webapps" unpackWARs="true" xmlValidation="false" xmlNamespaceAware="false"></host>中加入<Context path="/test" docBase="webdemo" debug="0" reloadable="true" />。docBase目录默认使用appBase="webapps"这个目录。也可以是绝对路径。
配置主目录,可以将path=""。
- 当项目没有放在webapps目录下时,可以在conf/Catalina/localhost新建一个XXX.XML文件。里面加入<Context docBase="E:webdemo" debug="0" reloadable="true" />。
注意:这里的path属性不需要设置,设置了也不会起作用的。
也可以使用该方法建立主目录指向另一个目录,例如:<Context docBase="E:webdemo" debug="0" reloadable="true" />命名为ROOT.xml,这样默认访问的主目录就被修改过了。
5.3 配置连接数
maxThreads:Tomcat使用线程来处理接收的每个请求。这个值表示Tomcat可创建的最大的线程数。
acceptCount:指定当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理。
minSpareThreads:Tomcat初始化时创建的线程数。
maxSpareThreads:一旦创建的线程超过这个值,Tomcat就会关闭不再需要的socket线程。
enableLookups:是否反查域名,取值为:true或false。为了提高处理能力,应设置为false
connectionTimeout:网络连接超时,单位:毫秒。设置为0表示永不超时,这样设置有隐患的。默认可设置为20000毫秒。
web server允许的最大连接数还受制于操作系统的内核参数设置,通常Windows是2000个左右,Linux是1000个左右。
5.4 配置内存大小
修改bin/catalina.bat中的set CATALINA_OPTS=-Xms64m -Xmx128m。
Xms指最小内存,Xmx指最大内存。
5.5 安全配置
1)将<Server port="8005" shutdown="SHUTDOWN">
SHUTDOWN修改为其他一些字符串。否则就容易被人给停止掉了。
2)屏蔽目录文件,修改conf/web.xml中的
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>true</param-value><!-- 改成false -->
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
3)访问日志设置记录路径Logs,在server.xml中加入
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log."
suffix=".txt" pattern="common" resolveHosts="false"/>
4)修改用户名、密码,修改conf/tomcat-users.xml
5)屏蔽后台管理入口
方法一:从控制用户和权限着手。废掉要管理权限的用户就可以了。
方法二:将conf/Catalina/localhost/manager.xml改名。
6)配置403,404,500错误页面
默认情况下,报出HTTP错误的时候会暴露tomcat版本号。如果不想暴露的话,就需要重新定义错误跳转页面。
<error-page>
<error-code>401</error-code>
<location>/401.jsp</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/404.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/500.jsp</location>
</error-page>
5.6 配置Log4j日志记录
项目中抛出的异常,抛到tomcat中的异常会被tomcat记录下来,存放至logs/localhost.yyyy-MM-dd.log文件中。
平时我们在项目中使用的log4j记录日志跟tomcat是没有任何关系的,是独立的一个程序,记录的文件是自定义的。
我们可以在tomcat中定义一个log4j的公共日志处理方式,这样在项目中就不需要在定义log4j的配置了。
1)将log4j-1.2.15.jar加入到commonlib目录。
2)将log4j.properties加入到commonclasses目录。
内容例如:
# Output pattern : date [thread] priority category - message
log4j.rootLogger=DEUBG, stdout, logfile
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.logfile.File=${catalina.home}/logs/tomcat_app.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
#3rd party library level
log4j.logger.org.hibernate.cache=ERROR
注意:我们项目中使用e.printStackTrace();输出的异常会在控制台输出来,但是,不会记录到tomcat日志中。
而且,也不会记录到log4j的日志中。要想记录到log4j日志中,必须使用log4j输出来。
所以,实际上web项目中进行异常处理应该将e.printStackTrace();写写法多改成log4j的形式才对!
但是,实际项目中很多项目多偷懒使用了e.printStackTrace();方式输出异常。当出现异常的时候在控制台上查看一下就可以了,也不考虑实际运行时候的维护。假如有人不小心关了控制台,那么,你不就看不到异常了吗?
个人介意使用log4j的形式记入web异常!
5.7 Tomcat乱码问题
Tomcat5跟Tomcat4对参数处理是不一样的,在Tomcat4中get与post的编码是一样的,所以只要在过滤器中通过request.setCharacterEncoding()设定一次就可以解决get与set的问题。然而,在Tomcat5中,get与post的处理是分开的,对get请求使用URIEncoding进行处理,对post使用request.setCharacterEncoding()处理。Tomcat5中,在server.xml的Connector元素增加了以下配置参数:URIEncoding:用来设定通过URI传递的
6、Tomcat 项目部署方式(手动)
- 打包项目:将项目构建为 WAR 文件(如
myapp.war
)。 - 复制文件:
cp myapp.war ${TOMCAT_HOME}/webapps/
- 自动解压:Tomcat 启动时自动解压 WAR 文件为目录(如
webapps/myapp/
)。 - 访问应用:通过
http://localhost:8080/myapp
访问。
三、tomcat 优化
1、内存优化
默认情况下Tomcat的相关内存配置较低,需要重新配置Tomcat的相关内存
# Linux
sed -i.bak '/set CLASSPATH/a\JAVA_OPTS="-server -XX:PermSize=512M -XX:MaxPermSize=1024m -Xms2048m -Xmx2048m"' "$TOMCAT_HOME/bin/catalina.sh"
# Windows
sed -i.bak '/set CLASSPATH/a\JAVA_OPTS="-server -XX:PermSize=512M -XX:MaxPermSize=1024m -Xms2048m -Xmx2048m"' "$TOMCAT_HOME/bin/catalina.bat"
- 参数说明
-server:启用 JDK的 server 版本;
-Xms:Java虚拟机初始化时堆的最小内存,一般与 Xmx配置为相同值,这样的好处是GC不必再为扩展内存空间而消耗性能;
-Xmx:Java虚拟机可使用堆的最大内存;
-XX:PermSize:Java虚拟机永久代大小;
-XX:MaxPermSize:Java虚拟机永久代大小最大值;
- 验证
- jps:用来显示本地的java进程,以及进程号,进程启动的路径等。
- jmap(jmap -heap 端口号):观察运行中的JVM 物理内存的占用情况,包括Heap size , Perm size 下载地址等。
2、配置优化
2.1 Connector 优化
Connector是连接器,负责接收客户的请求,以及向客户端回送响应的消息。所以 Connector的优化是重要部分。默认情况下 Tomcat只支持200线程访问,超过这个数量的连接将被等待甚至超时放弃,所以我们需要提高这方面的处理能力
<Connector port="8080" <!-- Tomcat监听端口 -->
protocol="HTTP/1.1" <!-- protocol 协议类型,可选类型有四种,分别为BIO(阻塞型IO),NIO,NIO2和APR -->
maxThreads="1000" <!-- 由该连接器创建的处理请求线程的最大数目-->
minSpareThreads="100" <!-- 线程的最小运行数目 -->
acceptCount="1000" <!-- acceptCount允许的最大连接数,应大于等于maxProcessors-->
maxConnections="1000" <!-- 在任何给定的时间内,服务器将接受和处理的最大连接数 -->
connectionTimeout="20000" <!-- 当请求已经被接受,但未被处理,也就是等待中的超时时间 -->
maxHttpHeaderSize="8192" <!-- 请求和响应的HTTP头的最大大小,以字节为单位指定 -->
tcpNoDelay="true" <!-- 如果为true,服务器socket会设置TCP_NO_DELAY选项,在大多数情况下可以提高性能 -->
compression="on" <!-- 是否启用gzip压缩,默认为关闭状态 -->
compressionMinSize="2048" <!-- compression="on"时启用此项。被压缩前数据的最小值,也就是超过这个值后才被压缩 -->
disableUploadTimeout="true" <!-- 这个标志允许servlet Container在一个servlet执行的时候,使用一个不同的,更长的连接超时 -->
redirectPort="8443"
enableLookups="false" <!-- 关闭DNS反向查询-->
URIEncoding="UTF-8" /> <!-- URL编码字符集 -->
更多参见:Tomcat官方网站
2.2 连接池
Executor 代表了一个线程池,可以在Tomcat组件之间共享。使用线程池的好处在于减少了创建销毁线程的相关消耗,而且可以提高线程的使用效率
<Service name="Catalina">
<Executor name="tomcatThreadPool" <!-- 线程池名称,用于 Connector中指定 -->
namePrefix="catalina-exec-" <!-- 所创建的每个线程的名称前缀,单独的线程名称为 namePrefix+threadNumber -->
maxThreads="1000" <!-- 池中最大线程数 -->
minSpareThreads="100" <!-- 活跃线程数,也就是核心池线程数,这些线程不会被销毁,会一直存在 -->
maxIdleTime="60000" <!-- 线程空闲时间,超过该时间后,空闲线程会被销毁 -->
maxQueueSize="Integer.MAX_VALUE" <!-- 在被执行前最大线程排队数目,默认为Int的最大值,也就是广义的无限 -->
prestartminSpareThreads="false" <!-- 启动线程池时是否启动 minSpareThreads部分线程 -->
threadPriority="5" <!-- 线程池中线程优先级,默认值为5,值从1到10 -->
className="org.apache.catalina.core.StandardThreadExecutor"/> <!-- 线程池实现类,未指定情况下,默认实现类为org.apache.catalina.core.StandardThreadExecutor。如果想使用自定义线程池首先需要实现 org.apache.catalina.Executor接口 -->
....
2.3 Listener
另一个影响Tomcat 性能的因素是内存泄露。Server标签中可以配置多个Listener,其中 JreMemoryLeakPreventionListener是用来预防JRE内存泄漏。此Listener只需在Server标签中配置即可,默认情况下无需配置,已经添加在 Server中
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
3、组件优化
3.1 APR (Apache Portable Runtime)
APR(Apache Portable Runtime)是一个高可移植库,它是Apache HTTP Server 2.x的核心。APR有很多用途,包括访问高级 IO功能(例如sendfile,epoll和OpenSSL),OS级别功能(随机数生成,系统状态等等),本地进程管理(共享内存,NT管道和UNIX sockets)。这些功能可以使Tomcat作为一个通常的前台WEB服务器,能更好地和其它本地web技术集成,总体上让Java更有效率作为一个高性能web服务器平台而不是简单作为后台容器。
APR的目的如其名称一样,主要为上层的应用程序提供一个可以跨越多操作系统平台使用的底层支持接口库。在早期的Apache版本中,应用程序本身必须能够处理各种具体操作系统平台的细节,并针对不同的平台调用不同的处理函数。随着Apache的进一步开发,Apache组织决定将这些通用的函数独立出来并发展成为一个新的项目。这样,APR的开发就从Apache中独立出来,Apache仅仅是使用APR而已。目前APR主要还是由Apache使用,不过由于APR的较好的移植性,因此一些需要进行移植的C程序也开始使用APR。
APR使得平台细节的处理进行下移。对于应用程序而言,它们根本就不需要考虑具体的平台,不管是Unix、Linux还是Window,应用程序执行的接口基本都是统一一致的。因此对于APR而言,可移植性和统一的上层接口是其考虑的一个重点。而APR最早的目的并不是如此,它最早只是希望将Apache中用到的所有代码合并为一个通用的代码库,然而这不是一个正确的策略,因此后来APR改变了其目标。有的时候使用公共代码并不是一件好事,比如如何将一个请求映射到线程或者进程是平台相关的,因此仅仅一个公共的代码库并不能完成这种区分。APR的目标则是希望安全合并所有的能够合并的代码而不需要牺牲性能。
3.2 Tomcat Native
Tomcat Native是 Tomcat可选组件,它可以让 Tomcat使用 Apache 的 APR包来处理包括文件和网络IO操作,从而提升性能及兼容性。
4、垃圾回收策略调优
垃圾回收的设置也是在catalina.sh中,调整JAVA_OPTS变量。
具体设置如下:
JAVA_OPTS="$JAVA_OPTS -Xmx3550m -Xms3550m -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100"
具体的垃圾回收策略及相应策略的各项参数如下:
串行收集器(JDK1.5以前主要的回收方式)
-XX:+UseSerialGC:设置串行收集器
并行收集器(吞吐量优先)
示例:
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100
-XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
-XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
-XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集
-XX:MaxGCPauseMillis=100:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
-XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。
并发收集器(响应时间优先)
示例:java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC
-XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。
-XX:+UseParNewGC: 设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。
-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片
打印类的加载情况及对象的回收情况:
这个可以通过配置JVM的启动参数,打印这些信息(到屏幕(默认也会到catalina.log中)或者文件),具体参数如下:
-XX:+PrintGC:输出形式:[GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K), 0.0650971 secs]
-XX:+PrintGCDetails:输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]
-XX:+PrintGCTimeStamps -XX:+PrintGC:PrintGCTimeStamps可与上面两个混合使用,输出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]
-XX:+PrintGCApplicationConcurrentTime:打印每次垃圾回收前,程序未中断的执行时间。可与上面混合使用。输出形式:Application time: 0.5291524 seconds
-XX:+PrintGCApplicationStoppedTime:打印垃圾回收期间程序暂停的时间。可与上面混合使用。输出形式:Total time for which application threads were stopped: 0.0468229 seconds
-XX:PrintHeapAtGC: 打印GC前后的详细堆栈信息
-Xloggc:filename:与上面几个配合使用,把相关日志信息记录到文件以便分析
-verbose:class 监视加载的类的情况
-verbose:gc 在虚拟机发生内存回收时在输出设备显示信息
-verbose:jni 输出native方法调用的相关情况,一般用于诊断jni调用错误信息
5、共享session处理
目前的处理方式有如下几种:
1).使用Tomcat本身的Session复制功能
参考http://ajita.iteye.com/blog/1715312(Session复制的配置)
方案的有点是配置简单,缺点是当集群数量较多时,Session复制的时间会比较长,影响响应的效率
2).使用第三方来存放共享Session
目前用的较多的是使用memcached来管理共享Session,借助于memcached-sesson-manager来进行Tomcat的Session管理
参考http://ajita.iteye.com/blog/1716320(使用MSM管理Tomcat集群session)
3).使用黏性session的策略
对于会话要求不太强(不涉及到计费,失败了允许重新请求下等)的场合,同一个用户的session可以由nginx或者apache交给同一个Tomcat来处理,这就是所谓的session sticky策略,目前应用也比较多
参考:http://ajita.iteye.com/blog/1848665(tomcat session sticky)
nginx默认不包含session sticky模块,需要重新编译才行(windows下我也不知道怎么重新编译)
优点是处理效率高多了,缺点是强会话要求的场合不合适
关于Tomcat的session数目:
使用JDK自带的jconsole可以比较明了的看到内存的使用情况,线程的状态,当前加载的类的总量等;
JDK自带的jvisualvm可以下载插件(如GC等),可以查看更丰富的信息。如果是分析本地的Tomcat的话,还可以进行内存抽样等,检查每个类的使用情况
6、添加JMS远程监控
对于部署在局域网内其它机器上的Tomcat,可以打开JMX监控端口,局域网其它机器就可以通过这个端口查看一些常用的参数(但一些比较复杂的功能不支持),同样是在JVM启动参数中配置即可,配置如下:
-Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
-Djava.rmi.server.hostname=192.168.71.38 设置JVM的JMS监控监听的IP地址,主要是为了防止错误的监听成127.0.0.1这个内网地址
-Dcom.sun.management.jmxremote.port=1090 设置JVM的JMS监控的端口
-Dcom.sun.management.jmxremote.ssl=false 设置JVM的JMS监控不实用SSL
-Dcom.sun.management.jmxremote.authenticate=false 设置JVM的JMS监控不需要认证
分析工具
IBM ISA,JProfiler、probe 等
四、SpringBoot内嵌Tomcat
1、概述
Spring Boot 内嵌 Tomcat 是指 Spring Boot 应用程序自带了一个 Tomcat 服务器,无需额外安装和配置。这使得应用程序可以作为一个独立的 Java 应用来运行,简化了部署过程。
2、工作原理
当您创建一个 Spring Boot 应用并使用 spring-boot-starter-web 依赖时,它默认包含了内嵌的 Tomcat 作为 Servlet 容器。
启动应用程序时,Spring Boot 会自动配置并启动 Tomcat,监听指定的端口(默认是8080),并处理传入的 HTTP 请求。
- 启动Spring应用程序:
- 在Spring Boot应用程序的main方法中,通常会调用SpringApplication.run(Application.class, args)来启动整个Spring应用程序。
- 创建和配置ApplicationContext:
- SpringApplication.run()方法内部会创建并配置一个ApplicationContext,这是Spring框架的核心容器,用于管理应用程序中的beans和生命周期。
- 自动配置Tomcat:
- 在ApplicationContext的创建和配置过程中,Spring Boot会根据类路径下的jar包和配置文件(如application.properties或application.yml)来自动配置Tomcat容器。
- onRefresh()方法:
- 在ApplicationContext的刷新(refresh)过程中,会调用ServletWebServerApplicationContext(或其子类)的onRefresh()方法。这是启动内嵌Tomcat容器的关键步骤之一。
- 在onRefresh()方法中,会调用createWebServer()方法来创建一个新的WebServer实例,这个实例实际上就是内嵌的Tomcat容器。
- createWebServer()方法:
- createWebServer()方法会根据ServletWebServerFactory(如TomcatServletWebServerFactory)来创建并配置Tomcat容器。
- 如果WebServer实例尚未存在,则通过ServletWebServerFactory创建一个新的WebServer实例,并注册一些必要的生命周期管理器和初始化器。
- 启动Tomcat容器:
- 在WebServer实例创建完成后,会调用其start()方法来启动Tomcat容器。
- 这个过程涉及到Tomcat内部组件的初始化和启动,包括Server、Service、Engine、Host和Connector等。
- 最终,Connector组件会启动并监听指定的端口,等待客户端的连接请求。
- 在WebServer实例创建完成后,会调用其start()方法来启动Tomcat容器。
- 应用程序启动完成:
- Tomcat容器启动后,Spring Boot应用程序就完成了启动过程,并可以开始处理HTTP请求。
3、优势
- 简化部署:无需单独安装和配置 Tomcat 服务器。
- 快速启动:由于没有了传统Web应用打包、部署的过程,应用可以更快地启动。
- 开发效率高:热部署功能支持代码修改后自动重启应用,提高了开发效率。
- 易于测试:内嵌服务器便于集成测试,因为它可以在任何环境中轻松启动和停止。
4、调优
调优内嵌的 Tomcat 主要是为了提高性能、增强安全性和优化资源使用。以下是一些常见的调优参数:
- 线程池设置:调整 Tomcat 的线程池大小可以影响应用程序处理并发请求的能力。
- server.tomcat.max-threads:设置最大线程数,默认值为200。计算公式:线程数=平均处理时间 * QPS / CPU核数
- server.tomcat.min-spare-threads:设置最小空闲线程数,默认值为10。
- 连接器设置:修改连接器属性可以提升网络层的性能。
- server.tomcat.accept-count:设置当所有线程都在忙时的最大等待队列长度。
- server.tomcat.connection-timeout:设置读取请求头的超时时间(以毫秒为单位)。
- 压缩设置:启用响应内容的压缩可以减少传输的数据量,但会增加 CPU 负担。
- server.compression.enabled=true
- server.compression.min-response-size=1024:设置触发压缩的最小响应大小(以字节为单位)。
- SSL/TLS 配置:如果需要通过 HTTPS 提供服务,可以配置 SSL/TLS。
- server.ssl.key-store:指定密钥库的位置。
- server.ssl.key-store-password:指定密钥库的密码。
- 会话管理:调整会话超时时间和存储方式。
- server.servlet.session.timeout:设置会话超时时间(以秒为单位)。
- 其他设置:还有许多其他的配置项可以根据应用的需求进行调整,比如 Multipart 文件上传限制等。
5、示例代码
下面是一个简单的 application.properties 或 application.yml 文件中的配置示例,用于调优内嵌的 Tomcat 服务器:
application.properties
# 线程池设置
server.tomcat.max-threads=400
server.tomcat.min-spare-threads=25
# 连接器设置
server.tomcat.accept-count=100
server.tomcat.connection-timeout=20000
# 压缩设置
server.compression.enabled=true
server.compression.min-response-size=2KB
# SSL/TLS 配置 (假设你有一个 JKS 格式的密钥库)
server.port=8443
server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=secret
server.ssl.key-alias=tomcat
# 会话管理
server.servlet.session.timeout=30m
application.yml
server:
tomcat:
max-threads: 400
min-spare-threads: 25
accept-count: 100
connection-timeout: 20s
compression:
enabled: true
min-response-size: 2KB
port: 8443
ssl:
key-store: classpath:keystore.jks
key-store-password: secret
key-alias: tomcat
servlet:
session:
timeout: 30m
动态配置:如果您想要在运行时动态地调整这些参数,可以通过实现 TomcatConnectorCustomizer 接口来定制连接器的行为:
import org.apache.catalina.connector.Connector;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TomcatConfig {
@Bean
public TomcatConnectorCustomizer connectorCustomizer() {
return new TomcatConnectorCustomizer() {
@Override
public void customize(Connector connector) {
// 设置连接器属性
connector.setProperty("property", "value");
// 更多自定义逻辑...
}
};
}
}