想象一家大型商场,门口有专门的引导员(Nginx)把顾客带到不同的专柜(Tomcat)。引导员负责分发客流,专柜负责服务顾客,这就是 Nginx + Tomcat 反向代理架构的精髓!
📑 目录
- 什么是反向代理?
- 名词解释(命令与概念)
- [Nginx 详解](#Nginx 详解)
- [Tomcat 详解](#Tomcat 详解)
- [Tomcat 与 JSP、Servlet、Java、Vue 的配置与使用](#Tomcat 与 JSP、Servlet、Java、Vue 的配置与使用)
- [Nginx 反向代理 Tomcat 实战](#Nginx 反向代理 Tomcat 实战)
- 静态资源缓存
- 性能优化
- 监控与日志
- [Nginx 线上常见问题与解决方案](#Nginx 线上常见问题与解决方案)
- 最佳实践
- 与其他软件配合使用及方案对比
- 总结与官方参考
🎯 什么是反向代理?
正向代理 vs 反向代理
🔒 反向代理
请求目标服务器
转发
转发
👤 客户端
🖥️ 反向代理
📦 后端服务器1
📦 后端服务器2
🔓 正向代理
请求
转发
👤 客户端
🖥️ 代理服务器
🌐 目标服务器
| 特性 | 正向代理 | 反向代理 |
|---|---|---|
| 服务对象 | 客户端 | 服务器 |
| 透明性 | 服务器知道是代理 | 客户端不知道 |
| 典型用途 | 科学上网、加速 | 负载均衡、缓存 |
| 生活类比 | 帮朋友买东西 | 银行大堂经理 |
反向代理的优势:
- 负载均衡
- 隐藏后端服务器
- SSL 终止
- 静态资源缓存
- 请求分发
📖 名词解释(命令与概念)
以下对文档中出现的指令、概念、术语 做简要解释,并配上生活例子与「为什么」,便于记忆与理解。Nginx 以 ngx_http_proxy_module、ngx_http_upstream_module 为准;Tomcat 以 Apache Tomcat 配置参考 为准。
反向代理相关
| 名词 | 含义 | 生活例子 | 为什么? |
|---|---|---|---|
| 反向代理 | 代理服务器替「后端服务器」接客,客户端以为在访问目标站 | 银行大堂经理:客户只跟经理打交道,经理再把业务分给后台柜员 | 为什么用反向?可隐藏后端、做负载均衡、SSL 终结、缓存,客户端无需改配置 |
| upstream | 定义一组后端服务器,供 proxy_pass 引用 | 后厨名单:经理按名单把单子分给不同厨师 | 为什么单独定义?可集中写 server、weight、backup、负载算法,多处 location 可复用同一组 |
| proxy_pass | 把当前请求转发到指定上游或 URL | 经理把单子交给某位厨师或某窗口 | 为什么有时带 URI 有时不带?带 URI 时会把 location 匹配部分替换成该 URI;不带则把完整请求 URI 转给后端 |
| proxy_set_header | 设置转发给后端时的请求头 | 经理在单子上注明「客人来自哪桌、原桌号」 | 为什么必须设 X-Real-IP、Host?后端要拿到真实客户端 IP 和原始域名做日志、限流、跨域;否则看到的是 Nginx 的 IP |
| X-Forwarded-For | 记录客户端及途经代理的 IP 链 | 快递单上的「经手站点」列表 | 为什么用 $proxy_add_x_forwarded_for?会追加当前客户端 IP,多级代理时后端能还原真实来源 |
Nginx 负载与超时
| 名词 | 含义 | 生活例子 | 为什么? |
|---|---|---|---|
| weight | 上游服务器的权重,轮询时分配比例 | 熟练工多派单、新手少派单 | 为什么默认 1?权重越高分到的请求越多,用于机器性能不一致时 |
| backup | 标记为备份节点,仅当主节点不可用时使用 | 替补队员,主力都挂了才上 | 为什么需要?主节点故障时自动切到 backup,避免全挂 |
| max_fails / fail_timeout | 连续失败几次视为不可用;不可用持续时间 | 连续几次叫不应就暂时不派单、过一段时间再试 | 为什么设?避免把请求一直打到已挂的后端,又能在恢复后自动加回 |
| proxy_connect_timeout | 与后端建立连接的超时 | 叫厨师应答的最长等待时间 | 为什么不能太长?后端卡住时尽快失败,释放 Nginx 连接 |
| proxy_read_timeout | 读后端响应的超时 | 等菜的最长等待时间 | 为什么动态请求要设大?某些接口耗时长,太小会 504;静态或 API 可设小 |
Tomcat 相关
| 名词 | 含义 | 生活例子 | 为什么? |
|---|---|---|---|
| Connector | Tomcat 对外接收请求的组件,每种协议一个 | 餐厅的「正门」(HTTP)和「员工通道」(AJP) | 为什么分 HTTP 和 AJP?HTTP 通用,浏览器、Nginx 都能连;AJP 二进制、效率高,多用于与 Apache/Nginx 同机或内网 |
| Engine | 请求处理引擎,下挂多个 Host | 后厨总管,管多个档口 | 为什么 defaultHost?请求的 Host 头匹配不到时,落到默认虚拟主机 |
| Host | 虚拟主机,按域名区分应用 | 同一餐厅的不同包厢区 | 为什么有 appBase?每个 Host 有自己的部署目录(如 webapps),互不干扰 |
| Context | 一个 Web 应用,对应一个 URL 路径 | 某道菜的制作流程 | 为什么 path="" 可做默认应用?空 path 表示根路径,访问 / 直接进该应用 |
| AJP | Apache JServ Protocol,二进制协议,多用于 Web 服务器与 Tomcat 之间 | 后厨内部传菜用的简写单,比对外菜单更省 | 为什么 Nginx 常用 HTTP 连 Tomcat?Nginx 官方模块只支持 HTTP 代理;AJP 需第三方模块,且 AJP 有安全风险需谨慎配置 secret |
相近概念对比(为什么容易混淆?)
| 对比项 | A | B | 区别一句话 | 何时用 A / 何时用 B |
|---|---|---|---|---|
| 正向代理 vs 反向代理 | 客户端通过代理访问外网 | 客户端访问代理,代理转给后端 | 谁「用」代理:客户用 vs 服务器用 | 翻墙、加速用正向;隐藏后端、负载均衡用反向 |
| proxy_pass 带 URI vs 不带 | proxy_pass http://backend/; | proxy_pass http://backend; | 带尾斜杠会替换 location 匹配部分 | 要改路径时带 URI(如 /api/ → 后端 /);原样转发不带 |
| 轮询 vs ip_hash vs least_conn | 依次派单 | 同一 IP 固定后端 | 按连接数最少派单 | 无状态用轮询;要会话保持用 ip_hash;长连接、耗时不一用 least_conn |
| Nginx 处理静态 vs Tomcat 处理动态 | Nginx 直接读文件或缓存 | 转发到 Tomcat 执行 Java | 谁干活:Nginx 自己干 vs 交给后端 | 静态用 Nginx 减轻 Tomcat;动态必须 Tomcat(或其它应用服务器) |
| HTTP Connector vs AJP Connector | 标准 HTTP,端口如 8080 | 二进制 AJP,端口如 8009 | 协议与端口不同 | 对外、Nginx 代理多用 HTTP;历史 Apache+Tomcat 同机常用 AJP |
🌐 Nginx 详解
什么是 Nginx?
Nginx 是一个高性能的 HTTP 和反向代理服务器,以事件驱动、低内存占用著称。
Nginx 背后的公司与产品线
| 项目/产品 | 说明 | 对应机构 |
|---|---|---|
| Nginx 开源版 | 免费、BSD 协议,社区维护,nginx.org 发布 | 由 F5, Inc. 下属团队主导开发(Nginx 项目源于俄罗斯开发者 Igor Sysoev,2004 年发布;2011 年成立 Nginx Inc. 提供商业支持;2019 年 Nginx Inc. 被 F5 Networks 收购,现为 F5 旗下产品线) |
| NGINX Plus | 商业版,带负载均衡健康检查、会话保持、监控、技术支持等 | F5, Inc.(原 Nginx Inc.) |
| OpenResty | 基于 Nginx 的 Lua 扩展平台,开源 | OpenResty Inc.(非 F5,独立项目) |
为什么了解公司有用? 开源版下载与文档在 nginx.org;商业支持、企业功能在 F5/NGINX 官网。选型时若需要官方 SLA、高级负载均衡或 WAF,可考虑 NGINX Plus;纯开源方案用社区版即可。

为什么 Nginx 高并发好? 采用事件驱动、非阻塞 I/O,少量 worker 进程即可处理大量连接,不像传统多进程/多线程那样「一个连接一个线程」占内存;适合做反向代理和静态资源服务。
Nginx vs Apache 对比
| 特性 | Nginx | Apache |
|---|---|---|
| 架构模型 | 事件驱动 | 进程/线程 |
| 并发性能 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| 内存使用 | ⭐⭐⭐⭐⭐ 低 | ⭐⭐ 中等 |
| 静态文件 | ⭐⭐⭐⭐⭐ 快 | ⭐⭐⭐ 中等 |
| 动态处理 | ⭐⭐⭐ 需要配置 | ⭐⭐⭐⭐⭐ 强大 |
| 配置复杂度 | ⭐⭐ 简单 | ⭐⭐⭐⭐ 复杂 |
| 生活类比 | 专业的服务员 | 瑞士全能服务员 |
Nginx 下载、安装与启动
下载来源
| 方式 | 说明 | 适用场景 |
|---|---|---|
| 官方包仓库 | nginx.org/packages 提供各发行版的 repo,安装得到官方构建的稳定版 | 推荐:版本清晰、便于升级与维护 |
| 发行版自带 | yum install nginx / apt install nginx 用系统源,版本可能较旧 |
不追求最新时可选用 |
| 源码编译 | nginx.org 下载 源码,./configure && make && make install |
需要特定模块(如 stub_status、自定义模块)或指定安装路径时 |
为什么推荐用官方 repo? 版本与 nginx.org 一致,修复与安全更新及时;系统自带的 nginx 可能缺少某些编译选项(如 http_stub_status_module)。
安装(以主流 Linux 为例)
CentOS / RHEL / Rocky / AlmaLinux(使用官方 nginx.org 源)
bash
# 1. 安装 yum-utils(用于管理 repo)
sudo yum install -y yum-utils
# 2. 添加 Nginx 官方 repo(以 mainline 为例,稳定版可用 nginx-stable)
sudo yum install -y https://nginx.org/packages/centos/$(rpm -E %rhel)/noarch/RPMS/nginx-release-centos-$(rpm -E %rhel)-0.el$(rpm -E %rhel).ngx.noarch.rpm
# 若上述 URL 不可用,可手动创建 /etc/yum.repos.d/nginx.repo,内容见 nginx.org/en/linux_packages.html
# 3. 安装 Nginx
sudo yum install -y nginx
# 4. 查看版本与安装路径
nginx -v
rpm -ql nginx | head -5
Debian / Ubuntu(使用官方 nginx.org 源)
bash
# 1. 安装依赖
sudo apt update
sudo apt install -y curl gnupg2 ca-certificates lsb-release debian-archive-keyring
# 2. 导入 Nginx 官方签名密钥并添加 repo(以 Ubuntu 24.04 为例,其他版本见 nginx.org/en/linux_packages.html)
curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo gpg --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] https://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" | sudo tee /etc/apt/sources.list.d/nginx.list
# 3. 安装 Nginx
sudo apt update
sudo apt install -y nginx
# 4. 查看版本
nginx -v
源码编译(简要)
bash
# 下载并解压(以 1.26.x 为例,请替换为官网最新稳定版)
wget https://nginx.org/download/nginx-1.26.2.tar.gz
tar -zxf nginx-1.26.2.tar.gz
cd nginx-1.26.2
# 常用选项:--with-http_stub_status_module 用于状态页,--prefix 指定安装目录
./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module
make
sudo make install
# 可执行文件在 /usr/local/nginx/sbin/nginx
为什么有时要源码编译? 包仓库里的 nginx 可能未包含 stub_status、realip 等模块,或需要第三方模块;编译时可 --add-module=... 加入并统一 --prefix 便于运维。
启动、停止、重载与开机自启
| 操作 | 命令(systemd) | 说明 | 为什么? |
|---|---|---|---|
| 启动 | sudo systemctl start nginx |
启动 Nginx 服务 | 安装后首次需 start;或 sudo /usr/sbin/nginx(视安装路径) |
| 停止 | sudo systemctl stop nginx |
停止服务 | 维护或排错时使用;会断开当前连接 |
| 重载配置 | sudo systemctl reload nginx 或 sudo nginx -s reload |
不中断服务,加载新配置 | 为什么用 reload?不杀进程,只让 worker 重新读配置,请求不中断,生产推荐 |
| 重启 | sudo systemctl restart nginx |
先停再起 | 为什么少用?会短暂断连;仅在 reload 无效或升级二进制时用 |
| 查看状态 | sudo systemctl status nginx |
是否在跑、最近日志 | 排错第一步看是否 active (running) |
| 开机自启 | sudo systemctl enable nginx |
设为开机自动启动 | 为什么开?机器重启后 Nginx 自动起来,无需人工 |
bash
# 常用命令汇总
sudo systemctl start nginx # 启动
sudo systemctl stop nginx # 停止
sudo systemctl reload nginx # 重载配置(推荐)
sudo systemctl restart nginx # 重启
sudo systemctl status nginx # 状态
sudo systemctl enable nginx # 开机自启
# 若为源码安装且未做 systemd 服务,可直接调用二进制
/usr/local/nginx/sbin/nginx # 启动
/usr/local/nginx/sbin/nginx -s stop # 停止
/usr/local/nginx/sbin/nginx -s reload # 重载
/usr/local/nginx/sbin/nginx -t # 仅测试配置文件语法,不启动
验证安装与运行
bash
# 1. 版本
nginx -v
# 或 nginx -V 查看编译参数与模块
# 2. 配置文件语法(改配置后先执行,再 reload)
sudo nginx -t
# 3. 进程与端口
ps aux | grep nginx
ss -tlnp | grep :80 # 或 netstat -tlnp | grep :80
# 4. 本机访问测试(若已监听 80)
curl -I http://127.0.0.1/
为什么先 nginx -t 再 reload? 配置有语法错误时,reload 可能不生效或导致异常;-t 只检查不启动,通过后再 reload 更安全。
默认路径速查(包安装)
| 项目 | 常见路径(以包安装为例,不同发行版可能略有差异) |
|---|---|
| 主配置 | /etc/nginx/nginx.conf |
| 子配置目录 | /etc/nginx/conf.d/ |
| 二进制 | /usr/sbin/nginx |
| 默认站点根目录 | /usr/share/nginx/html 或 /var/www/html |
| 日志 | /var/log/nginx/access.log、/var/log/nginx/error.log |
Nginx 版本说明(mainline 与 stable)
| 分支 | 版本号规律 | 说明 | 适用场景 |
|---|---|---|---|
| mainline | 奇数次小版本(如 1.27.x、1.29.x) | 当前开发主线,新特性、新修复先上,更新较频繁 | 需要新功能或愿意跟进最新修复时;生产若追求「求稳」可谨慎选用 |
| stable | 偶数次小版本(如 1.26.x、1.28.x) | 以稳定为主,只收经过验证的修复与安全补丁,新特性滞后 | 生产环境推荐:变更少、可预期,便于规划升级 |
为什么会有两条线? mainline 先试新东西,验证后再合到 stable,既保证 stable 可靠,又让愿意尝鲜的人能用上新能力。版本号规则见 nginx.org 各 repo 的说明。
如何选版本? 无特殊需求选 stable ;要某 mainline 才有的特性再选 mainline。安装时注意 repo 名称(如 nginx-mainline vs nginx)对应不同分支。官方下载页:nginx.org/en/download.html。
安装与运行常见问题
| 现象 | 可能原因 | 处理思路 | 为什么? |
|---|---|---|---|
| bind() to 0.0.0.0:80 failed (13: Permission denied) | ① 未用 root 启动;② SELinux 禁止监听 80 | ① 用 systemctl start nginx 或 sudo nginx,保证以 root 启动(Nginx 会再切到 worker 用户);② 见下 SELinux |
为什么 13?端口 <1024 在 Linux 上默认需 root 绑定;SELinux 可进一步限制即使 root 也不能用某端口 |
| SELinux 导致 80 无法绑定 | httpd_t 域未允许使用 80 端口 | `semanage port -l | grep http_port_t看是否含 80;若无则semanage port -a -t http_port_t -p tcp 80;或临时 setenforce 0` 测试(仅排错,不推荐长期关) |
| Address already in use / 80 端口被占用 | 本机已有程序监听 80(如 Apache、其他 Nginx) | `ss -tlnp | grep :80或lsof -i :80看占用进程;关掉冲突服务或改 Nginx 的listen` 端口 |
| nginx -t 报错 unknown directive / 模块不存在 | 当前安装未编译该模块(如 stub_status) | 用 nginx -V 看编译参数;包安装一般无 stub_status 时可换官方 repo 或源码编译加 --with-http_stub_status_module |
为什么包没有?发行版或旧 repo 的 nginx 可能精简了模块,官方包或自编更全 |
| reload 后不生效 | 配置有语法错误或 include 路径错误 | 每次改配置后先 nginx -t,通过再 systemctl reload nginx;看 error.log 是否有 "configuration file ... test failed" |
为什么 reload 不报错?reload 会尽量应用新配置,但有严重错误时可能只部分生效或静默失败,-t 能提前发现 |
| 403 Forbidden 访问静态页 | 文件权限或 SELinux 限制 Nginx 读文件 | 检查 root/alias 路径权限(至少对 Nginx 运行用户可读);SELinux 下可 chcon -R -t httpd_sys_content_t /path/to/web 或关目录的 SELinux 限制 |
为什么 403?Nginx 能连上但被内核或文件系统拒绝读文件,多为权限或 SELinux 类型不对 |
| yum/apt 安装时 404 或 repo 错误 | 发行版版本与 repo 不匹配、网络或 repo 未正确添加 | 对照 nginx.org/en/linux_packages.html 选对系统(CentOS 7/8/9、Ubuntu 版本等);检查 /etc/yum.repos.d/nginx*.repo 或 /etc/apt/sources.list.d/nginx* 的 baseurl/URL |
为什么 404?repo 的 URL 里通常带发行版代号,代号错则找不到包 |
SELinux 排查简要(RHEL/CentOS 等)
bash
# 查看 SELinux 状态
getenforce
# 查看是否因 SELinux 拒绝(查看最近审计日志)
grep nginx /var/log/audit/audit.log | audit2why
# 允许 httpd_t 使用 80 端口(需 policycoreutils-python-utils)
semanage port -l | grep http_port_t
semanage port -a -t http_port_t -p tcp 80
# 若为静态文件 403,给站点目录打标签
chcon -R -t httpd_sys_content_t /var/www/html
为什么不要长期关闭 SELinux? 关掉后整体安全策略失效,合规与安全风险大;应针对 Nginx 所需端口和目录做最小授权(semanage、setsebool 等),而不是 setenforce 0。
更多发行版与详细步骤见官方:Installing nginx、Linux packages。
Nginx 反向代理配置
nginx
# /etc/nginx/nginx.conf
http {
# 上游服务器定义
# 上游服务器定义(为什么用 upstream?集中管理后端列表和负载策略,多个 location 可复用)
upstream tomcat_servers {
# 负载均衡:轮询;weight 相等即均分;backup 仅当主节点都不可用时才用
server 192.168.1.20:8080 weight=1;
server 192.168.1.21:8080 weight=1;
server 192.168.1.22:8080 weight=1 backup;
# 或者使用 ip_hash(会话保持)
# ip_hash;
# server 192.168.1.20:8080;
# server 192.168.1.21:8080;
}
server {
listen 80;
server_name www.example.com;
location / {
# 反向代理到 Tomcat(为什么写 http://tomcat_servers?协议名 + upstream 名,不能写 IP 列表)
proxy_pass http://tomcat_servers;
# 传递真实 IP(为什么必须?Tomcat 日志、限流、风控需要真实客户端 IP,否则全是 Nginx 的 IP)
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 超时设置(为什么分三种?connect=建连,send=发请求,read=等响应;动态接口耗时长可把 read 调大)
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# 静态资源缓存
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
root /var/www/static;
expires 30d;
}
}
}
Nginx 负载均衡算法
⚖️ Nginx 负载均衡
🔄 轮询
round-robin
📉 最少连接
least_conn
🔑 IP 哈希
ip_hash
⚖️ 加权
weight
为什么有多种算法? 无状态接口用轮询即可;要会话保持(同一用户固定到同一台 Tomcat)用 ip_hash;后端处理时间差异大时用 least_conn 更公平;机器性能不一致时用 weight 分配比例。
| 算法 | 指令 | 说明 | 生活类比 |
|---|---|---|---|
| 轮询 | (默认) | 依次分发 | 发牌员轮流发牌 |
| least_conn | least_conn | 最少连接优先 | 找最空闲的服务员 |
| ip_hash | ip_hash | 按 IP 分配 | 固定服务员服务固定客户 |
| 加权 | weight=N | 权重分配 | 经验丰富的服务员多派单 |
🐘 Tomcat 详解
什么是 Tomcat?
Tomcat 是一个开源的 Java Servlet 容器,用于运行 Java Web 应用程序。
Tomcat 背后的项目与机构
| 项目/产品 | 说明 | 对应机构 |
|---|---|---|
| Apache Tomcat | 开源 Java Servlet/JSP 容器,实现 Jakarta EE(原 Java EE)部分规范,Apache 2.0 协议 | Apache Software Foundation (ASF) ,无单一商业公司「拥有」;由 Apache 基金会下的 Tomcat 项目 社区开发与发布 |
| 起源 | 最初为 Sun Microsystems 的 Servlet/JSP 参考实现,1999 年 Sun 将代码捐给 ASF,后成为顶级项目;Tomcat 10+ 实现 Jakarta EE,Tomcat 9 及以前实现 Java EE | 历史上与 Sun(后为 Oracle 收购的 Java 业务)相关,现完全由 ASF 独立维护 |
| 商业支持 | Tomcat 本身无官方商业版;企业如需商业支持可选用 Red Hat JBoss EAP 、VMware Tanzu 、Oracle WebLogic 等基于或兼容 Tomcat 技术的商业产品 | 各厂商(Red Hat、VMware、Oracle 等)提供付费支持,并非「Tomcat 公司」 |
为什么了解机构有用? 官方下载、文档均在 tomcat.apache.org;无「Tomcat 公司」收费版,选型时若需要企业级 SLA 或集成套件,需看第三方发行版或商业应用服务器。
🐘 Tomcat 架构
🖥️ Server
服务器
📋 Service
服务
🔌 Connector
连接器
⚙️ Engine
引擎
🏠 Host
主机
📂 Context
应用
Tomcat 核心组件
| 组件 | 说明 | 生活类比 | 为什么? |
|---|---|---|---|
| Server | 整个 Tomcat 容器 | 整个餐厅 | 为什么是顶层?一个 JVM 里只有一个 Server,关掉 Server 即关掉 Tomcat |
| Service | 服务组件,包含 Connector + Engine | 餐厅服务部门(迎宾+后厨) | 为什么有 Service?可把多个 Connector 绑到同一 Engine,例如 HTTP 和 AJP 共用一套应用 |
| Connector | 连接器,接收请求并交给 Engine | 餐厅入口大门 | 为什么分端口?8080 给 HTTP、8009 给 AJP,不同协议不同入口 |
| Engine | 引擎,按 Host 头选 Host 并处理请求 | 后厨管理 | 为什么有 defaultHost?请求的 Host 不匹配任何 Host 时,用 defaultHost 避免 404 |
| Host | 虚拟主机,对应域名,有 appBase | 餐厅分区(包厢名) | 为什么多个 Host?一机多站点时按域名区分,各用各的 webapps 目录 |
| Context | 应用上下文,一个 Web 应用 | 具体的餐桌/菜单 | 为什么 path=""?表示根应用,访问 / 即该应用,常用于单应用部署 |
Tomcat 连接器类型
| 连接器 | 协议 | 说明 | 为什么选? |
|---|---|---|---|
| HTTP | HTTP/1.1 | 标准 HTTP 连接,端口如 8080 | 为什么常用?Nginx、浏览器、压测工具都直接连;无需额外模块,配置简单 |
| AJP | AJP/1.3 | 二进制协议,端口如 8009,多用于与 Apache 配合 | 为什么有 AJP?历史 Apache+Tomcat 同机时用 AJP 效率高;Nginx 需第三方模块才支持,且要配 secret 防未授权 |
| HTTP/2 | HTTP/2 | HTTP/2 连接 | 为什么可选?多路复用、头部压缩,适合现代浏览器;需 TLS 或 h2c 配置 |
☕ Tomcat 与 JSP、Servlet、Java、Vue 的配置与使用
Tomcat 是 Java 应用服务器,用来跑 Servlet 和 JSP ;前端若用 Vue 做前后端分离,通常 Vue 打包成静态资源由 Nginx 或 Tomcat 提供,接口由 Tomcat 提供。本节简要说明四者与 Tomcat 的配置和使用方式。
整体关系一览
🐘 Tomcat
🖥️ Nginx(可选)
👤 浏览器
请求 HTML/JS/API
静态 Vue/HTML
/api → 代理
静态资源
Vue 打包文件
Servlet
接口
JSP
页面
| 技术 | 和 Tomcat 的关系 | 典型用法 |
|---|---|---|
| Java | Tomcat 本身用 Java 跑,应用也是 Java(Servlet/JSP) | 装好 JDK、设 JAVA_HOME,应用打成 WAR 或目录放到 webapps |
| Servlet | Tomcat 是 Servlet 容器,请求由 Servlet 处理 | 写 Java 类实现 Servlet 接口,在 web.xml 或注解里配置 URL 映射 |
| JSP | JSP 会被编译成 Servlet,由 Tomcat 的 Jasper 引擎执行 | 把 .jsp 文件放到 webapp 下,通过 URL 访问即可 |
| Vue | Vue 打包后是静态 HTML/JS/CSS,可放 Tomcat 或 Nginx;接口调 Tomcat 提供的 Servlet/Spring 等 | 开发时 Vue devServer 代理到 Tomcat;生产可 Nginx 放静态 + 反向代理 /api 到 Tomcat |
Tomcat 与 Java:运行环境与启动参数
Tomcat 依赖 JDK (建议 JDK 8 或 11+,与 Tomcat 版本匹配)。需要配置两件事:JAVA_HOME 和 JVM 启动参数。
1. 设置 JAVA_HOME
bash
# Linux / macOS:在 /etc/profile 或 ~/.bashrc 中
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk
export PATH=$JAVA_HOME/bin:$PATH
# 验证
java -version
echo $JAVA_HOME
2. Tomcat 启动参数(setenv.sh / setenv.bat)
Tomcat 启动脚本会读取 CATALINA_HOME/bin/setenv.sh(Linux)或 setenv.bat(Windows),在这里设 JVM 参数,不修改 catalina.sh 本身。
bash
# 创建 $CATALINA_HOME/bin/setenv.sh(无则新建)
# 示例:堆内存、GC、编码
export JAVA_OPTS="$JAVA_OPTS -Xms512m -Xmx1024m"
export JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF-8"
export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC"
3. 可选:CATALINA_OPTS 与 JAVA_OPTS 区别
- JAVA_OPTS:所有 Tomcat 进程(包括 stop)都会用,一般放通用参数。
- CATALINA_OPTS :仅
start/run时用,一般放运行时的堆、GC 等。
bash
# 仅 start 时生效的堆与 GC
export CATALINA_OPTS="$CATALINA_OPTS -Xms512m -Xmx1024m -XX:+UseG1GC"
Tomcat 与 Servlet:部署与 URL 映射
Servlet 是 Java 里处理 HTTP 请求的规范,Tomcat 根据配置把某个 URL 交给哪个 Servlet 处理。
1. 标准目录结构(WAR 解压后等价)
myapp/
├── WEB-INF/
│ ├── web.xml # 配置 Servlet、过滤器、欢迎页等
│ ├── classes/ # 你的 .class 或源码(若用 IDE 导出)
│ └── lib/ # 依赖 jar
├── index.html # 静态页面
└── hello.jsp # JSP 页面
2. web.xml 里配置 Servlet(传统方式)
xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.example.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/api/hello</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
访问:http://host:8080/myapp/api/hello 会由 HelloServlet 处理。
3. 注解方式(Servlet 3.0+,无需在 web.xml 写 mapping)
java
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/api/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/plain;charset=UTF-8");
resp.getWriter().write("Hello from Servlet");
}
}
4. 部署方式
- 方式 A :把
myapp目录放到 Tomcat 的webapps/下,访问路径为/myapp。 - 方式 B :打成
myapp.war放到webapps/,Tomcat 会自动解压成myapp/。 - 方式 C:在 server.xml 的 Host 下加 Context,指定 docBase 指向磁盘上的目录(如独立部署目录)。
xml
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
<Context path="/myapp" docBase="/opt/myapp" />
</Host>
Tomcat 与 JSP:放置与访问
JSP 本质是「带 Java 片段的 HTML」,第一次访问时会被编译成 Servlet 再执行。
1. 放哪里
- 放在应用的根目录或子目录下,不要 放在
WEB-INF里(WEB-INF 下的 JSP 不能通过 URL 直接访问,只能内部转发)。 - 例如:
myapp/hello.jsp、myapp/page/foo.jsp。
2. 如何访问
- 直接访问:
http://host:8080/myapp/hello.jsp。 - 若配置了欢迎页且是 JSP,访问
http://host:8080/myapp/也会打开该 JSP。
3. 简单示例
jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>JSP 示例</title></head>
<body>
<h1>当前时间:<%= new java.util.Date() %></h1>
<%
String name = request.getParameter("name");
if (name != null) {
out.println("<p>Hello, " + name + "</p>");
}
%>
</body>
</html>
4. 与 Servlet 的关系
- 请求先到 Tomcat,根据 URL 决定是走 JSP 还是 Servlet。
- JSP 第一次访问时由 Jasper 编译成
*_jsp.java再编译成 class,之后以 Servlet 形式运行;可理解为「写 JSP = 写一种自动生成的 Servlet」。
Tomcat 与 Vue:前后端分离的配置与使用
Vue 是前端框架,打包后得到静态文件(HTML、JS、CSS)。后端接口通常由 Tomcat(Servlet、Spring MVC 等)提供。有两种常见部署方式。
方式一:Vue 打包结果放 Tomcat,接口同域
-
Vue 执行
npm run build,生成dist/(默认有index.html和assets/)。 -
把
dist里的内容放到 Tomcat 应用的根目录,例如:webapps/myapp/
├── index.html
├── assets/
│ ├── index-xxx.js
│ └── index-xxx.css
└── WEB-INF/
└── ...(后端 Servlet、JSP 等) -
前端请求接口用相对路径,如
/myapp/api/xxx,和 Servlet 的 url-pattern 一致。 -
若 Vue 的 publicPath 是
/myapp/,打包后资源路径会带/myapp/,与上述结构一致。
Vue 侧(vue.config.js)示例:
javascript
module.exports = {
publicPath: '/myapp/',
devServer: {
port: 8081,
proxy: {
'/myapp/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
}
- 开发时:访问
http://localhost:8081,请求/myapp/api/*会被代理到 Tomcat 的 8080。 - 生产:同一应用下既有静态(Vue)也有
/myapp/api(Tomcat Servlet)。
方式二:Vue 放 Nginx,接口反向代理到 Tomcat(更常见)
- Nginx 提供 Vue 的静态文件(root 指向打包后的
dist或单独目录)。 - 接口统一用前缀(如
/api)反向代理到 Tomcat。
nginx
server {
listen 80;
server_name www.example.com;
root /var/www/vue-dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://tomcat_cluster;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
- Vue 里接口 baseURL 设为
/api(或完整域名),这样生产环境请求会发到 Nginx,再由 Nginx 转给 Tomcat。 - 开发时同样用 devServer proxy 把
/api转到本机 Tomcat。
小结
| 场景 | 建议 |
|---|---|
| 小项目、希望一个 Tomcat 同时提供页面和接口 | Vue 打包到 Tomcat 应用目录,publicPath 与 context path 一致;接口用相对路径 /myapp/api/... |
| 生产环境、前后端分离清晰 | Nginx 托管 Vue 静态,/api 反向代理到 Tomcat;Vue 的 baseURL 用 /api 或完整域名 |
| 本地开发 | Vue devServer proxy 到 http://localhost:8080,避免跨域 |
🚀 Nginx 反向代理 Tomcat 实战
环境拓扑
🐘 Tomcat 层
🖥️ Nginx 层
🌐 Nginx
192.168.1.10:80
反向代理
📦 Tomcat1
192.168.1.20:8080
📦 Tomcat2
192.168.1.21:8080
📦 Tomcat3
192.168.1.22:8080
Nginx 配置
nginx
# /etc/nginx/conf.d/proxy_tomcat.conf
upstream tomcat_cluster {
# 负载均衡配置
server 192.168.1.20:8080 weight=1 max_fails=2 fail_timeout=30s;
server 192.168.1.21:8080 weight=1 max_fails=2 fail_timeout=30s;
server 192.168.1.22:8080 weight=1 max_fails=2 fail_timeout=30s;
# 健康检查(需要商业版或 openresty)
# check interval=3000 rise=2 fall=3 timeout=1000;
}
server {
listen 80;
server_name www.example.com;
# 访问日志
access_log /var/log/nginx/tomcat_access.log;
error_log /var/log/nginx/tomcat_error.log;
# 根路径代理到 Tomcat
location / {
proxy_pass http://tomcat_cluster;
# 传递原始请求头
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket 支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 超时配置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 缓冲配置
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
}
# 静态资源直接由 Nginx 处理
location ~* \.(jpg|jpeg|png|gif|ico|css|js|html|htm)$ {
root /var/www/static;
expires 30d;
access_log off;
}
}
Tomcat 配置
xml
<!-- /etc/tomcat/server.xml -->
<!-- 配置 AJP 连接器 -->
<Connector port="8009"
protocol="AJP/1.3"
redirectPort="8443"
secret="your-secret"
address="192.168.1.20" />
<!-- 配置 HTTP 连接器 -->
<Connector port="8080"
protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
address="192.168.1.20"
maxThreads="500"
minSpareThreads="50"
enableLookups="false" />
<!-- 配置 Engine -->
<Engine name="Catalina" defaultHost="localhost">
<!-- 配置虚拟主机 -->
<Host name="localhost"
appBase="webapps"
unpackWARs="true"
autoDeploy="true">
<!-- Context 配置 -->
<Context path="" docBase="/opt/myapp" />
</Host>
</Engine>
会话保持
📌 会话保持需求
🔑 ip_hash
🍪 sticky_cookie
🔄 session 复制
同一 IP 到同一 Tomcat
基于 Cookie 分配
Session 集群复制
为什么需要会话保持? 用户登录后 Session 存在某台 Tomcat 上,若下次请求被分到另一台,会丢登录状态。ip_hash 或 sticky 让同一用户始终落到同一台;或通过 Tomcat 集群把 Session 复制到多台,任一台都能识别。
方法1:ip_hash
nginx
upstream tomcat_cluster {
ip_hash; # 会话保持
server 192.168.1.20:8080;
server 192.168.1.21:8080;
}
方法2:sticky_cookie(商业版)
nginx
upstream tomcat_cluster {
sticky cookie srv_id expires=1h domain=.example.com path=/;
server 192.168.1.20:8080;
server 192.168.1.21:8080;
}
方法3:Tomcat Session 复制
xml
<!-- 在 Tomcat 的 context.xml 中配置 -->
<Manager className="org.apache.catalina.ha.session.BackupManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"
mapSendOptions="6"/>
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService"
address="228.0.0.4"
port="45564"
frequency="500"
dropTime="3000"/>
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="auto"
port="4000"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>
<Sender className="org.apache.catalina.tribes.transport.nio.NioSender"
address="auto"
port="4000"
autoBind="true"
connectTimeout="5000"
maxWait="10000"/>
</Channel>
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
Tomcat 集群配置与架构
多台 Tomcat 在 Nginx 后做负载均衡即构成「Tomcat 集群」。集群要解决两类问题:高可用/分流 和 Session 一致性(有状态应用)。本节说明集群架构、Session 方案选择,以及 Tomcat 自带的集群与 Session 复制配置。
集群架构示意
📡 可选:Session 同步
🐘 Tomcat 集群
🖥️ 接入层
Nginx
负载均衡
Tomcat-1
jvmRoute=node1
Tomcat-2
jvmRoute=node2
Tomcat-3
jvmRoute=node3
多播/组播
或 TCP
- 无 Session 复制:Nginx 用 ip_hash 或 sticky,同一用户始终到同一台 Tomcat,无需多机同步 Session。
- 有 Session 复制:多台 Tomcat 通过 Tomcat 集群(多播或 TCP)同步 Session,任意一台都能识别同一用户;Nginx 可用轮询。
集群下 Session 的三种做法
| 方式 | 做法 | 优点 | 缺点 | 适用 |
|---|---|---|---|---|
| 会话保持 | Nginx ip_hash 或 sticky,同一用户固定到一台 Tomcat | 配置简单、无集群通信 | 该节点宕机则该用户 Session 丢失;扩容要 rehash | 节点少、可接受单点 Session 丢失 |
| Tomcat Session 复制 | 每台 Tomcat 加入同一 Cluster,Session 变更通过组播/TCP 同步到其他节点 | 无单点、任意节点可服务 | 占带宽和内存、节点多时复制量大;要开多播或端口 | 节点数不多(如 2~4)、内网稳定 |
| 外部 Session 存储 | Session 存 Redis/Memcached,所有 Tomcat 读同一存储 | 扩容方便、Session 与进程解耦 | 依赖 Redis、序列化与网络延迟 | 多机水平扩展、已有 Redis 时 |
Tomcat 集群配置要点
1. 为每台 Tomcat 设置 jvmRoute
同一集群内每台 Tomcat 的 jvmRoute 必须唯一,用于 Session 亲和与故障转移时识别节点。在 server.xml 的 Engine 上配置:
xml
<Engine name="Catalina" defaultHost="localhost" jvmRoute="node1">
- 机器 1:
jvmRoute="node1" - 机器 2:
jvmRoute="node2" - 机器 3:
jvmRoute="node3"
若使用 Nginx sticky,Cookie 里会带上类似 node1 的路由信息,Nginx 可据此把请求送到对应节点。
2. 在 server.xml 中启用 Cluster
在 server.xml 的 Engine 内增加 Cluster 配置,使该 Engine 下的应用可参与 Session 复制。下面示例使用多播做成员发现与复制(同一网段、允许多播时可用):
xml
<Engine name="Catalina" defaultHost="localhost" jvmRoute="node1">
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="8">
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"/>
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService"
address="228.0.0.4"
port="45564"
frequency="500"
dropTime="3000"/>
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="auto"
port="4000"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>
<Sender className="org.apache.catalina.tribes.transport.nio.NioSender"
address="auto"
connectTimeout="5000"
maxWait="10000"/>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
filter=""/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
<Realm>...</Realm>
<Host name="localhost" appBase="webapps" ...>
...
</Host>
</Engine>
- DeltaManager:Session 变更(delta)同步到集群内所有其他节点;节点多时复制量大。
- BackupManager:每台 Session 只备份到另一台,复制量小,但单台宕机可能丢部分 Session(取决于备份策略)。
- Membership :多播地址
address/port集群内必须一致,且防火墙/云环境需放行多播或改用 TCP 成员发现。 - Receiver :
port="4000"为集群通信端口,每台机器端口可相同(不同 IP)或不同(同机多实例);防火墙需放行。 - ReplicationValve :在响应提交前把 Session 变更复制出去;
filter可过滤不复制的内容。
3. 应用开启分布式 Session(web.xml)
参与集群复制的 Web 应用需在 WEB-INF/web.xml 中声明 distributable:
xml
<web-app ...>
<distributable/>
...
</web-app>
否则 Tomcat 不会把该应用的 Session 纳入集群复制。且 Session 里放的对象需要可序列化 (实现 java.io.Serializable),否则复制会报错。
4. 多播与防火墙注意
- 多播地址如
228.0.0.4、端口45564:集群内所有节点要能收到多播包;云主机或 Docker 常不支持多播,需改用 StaticMember 等 TCP 成员配置。 - 集群通信端口(如 Receiver 的 4000):节点之间要能互通,防火墙需放行。
部署与运维要点
| 项目 | 说明 |
|---|---|
| 同一应用 | 每台 Tomcat 部署同一版本 WAR/目录,避免请求到不同节点时行为不一致。 |
| jvmRoute 唯一 | 每台 jvmRoute 不同,且与 Nginx 的 upstream 对应(若用 sticky)。 |
| 多播/端口 | 多播不通时改用 TCP 成员;Receiver 端口不冲突(多实例同机时改不同 port)。 |
| 序列化 | Session 中对象必须可序列化,避免大对象或不可序列化对象。 |
| 监控 | 观察集群视图(Tomcat Manager 或 JMX)、复制是否失败、网络与内存占用。 |
与 Redis Session 的简单对比
- Tomcat Session 复制:不依赖外部组件,Session 存在各节点内存,通过 Tomcat 集群通道同步;适合节点少、内网稳定。
- Redis Session:Session 存 Redis,所有 Tomcat 无状态;需应用或 Filter 把 Session 存 Redis(如 Spring Session),适合多机扩展、已有 Redis 的场景。
若已有 Redis,多数项目更倾向用 Redis Session 做多机 Session 共享;若无 Redis 且只有 2~4 台 Tomcat,可用 Tomcat 自带的 Session 复制 做集群。
🖼️ 静态资源缓存
缓存策略
静态文件
动态请求
👤 客户端请求
资源类型?
📁 Nginx 直接返回
有缓存则返回缓存
➡️ 转发到 Tomcat
减少 Tomcat 负载
Tomcat 处理
为什么动静分离? 静态文件(图片、CSS、JS)由 Nginx 读磁盘或缓存返回,比经过 Tomcat 解析、再读文件省 CPU 和线程;Tomcat 专注处理 JSP、Servlet、接口,整体吞吐更高。
Nginx 静态资源配置
nginx
server {
listen 80;
server_name www.example.com;
# 静态文件缓存配置(为什么 1y?图片/CSS/JS 带版本号时长期缓存安全,immutable 告诉浏览器不必再验)
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|eot|svg)$ {
root /var/www/static;
expires 1y;
access_log off;
add_header Cache-Control "public, immutable";
}
# HTML 文件缓存(为什么 7d?HTML 可能引用新资源,不宜过长;无版本号时适中即可)
location ~* \.html$ {
root /var/www/static;
expires 7d;
add_header Cache-Control "public";
}
# 禁止缓存(为什么 jsp/do no-cache?动态内容每次可能不同,缓存会导致看到旧数据或错乱)
location ~* \.(php|jsp|do)$ {
proxy_pass http://tomcat_cluster;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
}
📊 性能优化
Nginx 优化
nginx
# /etc/nginx/nginx.conf
user nginx;
worker_processes auto; # 为什么 auto?一般等于 CPU 核数,充分利用多核
worker_rlimit_nofile 65535; # 为什么设?每个连接占一个 fd,默认限制可能不够高并发
events {
worker_connections 10240; # 为什么 10240?单 worker 最大连接数,总连接 ≈ worker_processes × worker_connections
use epoll; # 为什么 epoll?Linux 下高效多路复用,高并发必备
multi_accept on; # 为什么 on?一次 accept 多个新连接,减少唤醒次数
}
http {
# 基础优化(为什么 sendfile?内核直接在内核态把文件写到 socket,少一次用户态拷贝,静态文件更快)
sendfile on;
tcp_nopush on; # 与 sendfile 搭配,攒包再发,减少小包
tcp_nodelay on; # 关闭 Nagle,小响应及时发,为什么?动态内容往往小包,延迟敏感
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 50M; # 为什么限制?防止大 body 占满内存或磁盘,按业务上传需求设
# Gzip 压缩(为什么开?文本类响应体积明显减小,带宽和延迟都改善;为什么 gzip_min_length?太小的包压缩反而变大)
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# 隐藏版本号(为什么?减少暴露版本信息,降低被针对攻击的概率)
server_tokens off;
}
Tomcat 优化
xml
<!-- /etc/tomcat/server.xml -->
<Connector port="8080"
protocol="HTTP/1.1"
maxThreads="500"
minSpareThreads="50"
maxSpareThreads="200"
acceptCount="500"
acceptorThreadCount="20"
connectionTimeout="20000"
enableLookups="false"
compression="on"
compressionMinSize="2048"
noCompressionUserAgents="gozilla-trident"
compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json"
URIEncoding="UTF-8" />
<!-- JVM 优化 -->
<!-- 在 catalina.sh 或 setenv.sh 中添加 -->
JAVA_OPTS="$JAVA_OPTS -Xms2048m -Xmx2048m -XX:PermSize=512m -XX:MaxPermSize=512m -XX:+UseConcMarkSweepGC -XX:+UseCMSClassLoader"
📈 监控与日志
Nginx 监控
| 命令/配置 | 作用 | 为什么常用? |
|---|---|---|
systemctl status nginx |
看 Nginx 是否在跑 | 为什么先看?进程挂了则所有请求失败,排错第一步 |
| `netstat -antp | grep :80 | wc -l` |
tail -f .../tomcat_access.log |
实时看访问日志 | 为什么看日志?404、502、慢请求、IP 分布都在这里,排查问题必看 |
stub_status on |
输出简单状态页(需编译 --with-http_stub_status_module) | 为什么有用?可看到 active/reading/writing/waiting 连接数,监控大盘常用 |
bash
# 1. 基础状态查看
systemctl status nginx
# 2. 连接数统计
netstat -antp | grep :80 | wc -l
# 3. 访问日志分析
tail -f /var/log/nginx/tomcat_access.log
# 4. Nginx 状态模块(需要编译时添加)
location /nginx_status {
stub_status on;
access_log off;
allow 192.168.1.0/24;
deny all;
}
Tomcat 监控
bash
# 1. 查看 Tomcat 状态
systemctl status tomcat
# 2. 查看 Tomcat 日志
tail -f /opt/tomcat/logs/catalina.out
# 3. Tomcat 管理界面
# 访问 http://ip:8080/manager
🔧 Nginx 线上常见问题与解决方案
实际运行中常遇到 502/504、上传失败、连接数爆满、响应慢等问题。本节按现象 → 原因 → 排查 → 配置方案整理,便于快速定位与修复。
问题定位思路(概览)
🚨 线上异常
看 Nginx 状态码/日志
502 Bad Gateway
504 Gateway Timeout
413 / 499 / 5xx
慢 / 超时 / 连接满
查 upstream 是否可达
max_fails/fail_timeout
查 proxy_read_timeout
与后端耗时
client_max_body_size
或客户端断开 499
缓冲/连接数/限流
502 Bad Gateway
现象:用户看到 502,Nginx 把请求转给后端时失败。
常见原因与处理:
| 原因 | 排查方法 | 解决方案(配置示例) |
|---|---|---|
| 后端 Tomcat 未启动或宕机 | curl -I http://后端IP:8080/、看 Tomcat 进程与日志 |
启动 Tomcat;在 upstream 中配多台 + max_fails/fail_timeout,避免把请求打到已挂节点 |
| 后端端口/防火墙不通 | telnet 后端IP 8080、本机 firewall/iptables |
放行应用端口;确保 Nginx 能访问后端网段 |
| 连接被拒绝(connection refused) | Nginx error.log 有 "connect() failed (111: Connection refused)" | 同上,并检查 Tomcat 的 Connector address 是否绑定到 0.0.0.0 或正确 IP |
| upstream 全部被摘除 | 所有 server 都因 max_fails 被标记 down | 恢复至少一台后端;或临时调大 max_fails、缩短 fail_timeout,或使用 backup 节点 |
推荐 upstream 配置(减少 502):
nginx
upstream tomcat_cluster {
# 连续 2 次失败则 30s 内不再转发到该节点,避免持续 502
server 192.168.1.20:8080 weight=1 max_fails=2 fail_timeout=30s;
server 192.168.1.21:8080 weight=1 max_fails=2 fail_timeout=30s;
server 192.168.1.22:8080 weight=1 max_fails=2 fail_timeout=30s backup;
# 与后端保持长连接,减少建连开销(需 Nginx 1.1+)
keepalive 32;
}
server {
location / {
proxy_pass http://tomcat_cluster;
proxy_http_version 1.1;
proxy_set_header Connection ""; # 与 keepalive 配合
proxy_connect_timeout 5s; # 建连快失败,不长时间挂住
proxy_read_timeout 60s; # 按业务调整,避免后端慢导致长时间占连接
}
}
为什么 max_fails 不宜过大? 设太大则故障节点被摘除过慢,用户会持续打到坏节点;过小则网络抖动就摘掉,一般 2~3 次即可。
504 Gateway Timeout
现象:用户等待较久后出现 504,表示 Nginx 在规定时间内没拿到后端完整响应。
常见原因 :后端处理时间超过 Nginx 的 proxy_read_timeout(默认 60s),或后端卡死。
排查 :看 Nginx error.log 是否有 "upstream timed out";看 Tomcat 日志是否有慢 SQL、死锁、GC 长等。
解决方案:
nginx
# 对耗时较长的接口单独加大读超时(按业务区分 location)
location /api/ {
proxy_pass http://tomcat_cluster;
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 120s; # 长任务接口可适当调大,如 120s 或 300s
}
# 普通页面保持较短超时,避免慢请求占满连接
location / {
proxy_pass http://tomcat_cluster;
proxy_read_timeout 30s;
}
为什么不能全局设很大? 若所有请求都 300s 超时,慢或卡住的后端会占满 Nginx 与后端连接,影响其他请求;应对真正慢的接口单独设 location 和超时。
413 Request Entity Too Large
现象:上传较大文件时返回 413。
原因 :请求体超过 Nginx 的 client_max_body_size(默认 1m)。
解决方案:
nginx
# 在 http 或 server 或 location 中设置(单位 k/m/g)
client_max_body_size 50m;
# 仅上传接口放大
location /api/upload {
client_max_body_size 100m;
proxy_pass http://tomcat_cluster;
proxy_request_buffering on; # 先收完再转给后端,避免后端超时(可选)
}
499 Client Closed Request
现象 :Nginx 日志中状态码为 499,表示客户端在 Nginx 收到后端响应之前就断开了(如用户关页面、超时重试)。
原因:前端/客户端超时时间小于 Nginx 或后端处理时间;或用户主动关闭。
可做措施:
- 适当增大
proxy_read_timeout,减少「后端还没返回客户端就断」的情况(需与业务平衡)。 - 在日志中记录 499 便于区分「用户取消」与「后端太慢」:
access_log ... upstream_status等。 - 优化后端与接口耗时,从根上减少长时间占用。
为什么 499 不能完全消除? 用户关页面、网络断开等必然会产生;可通过监控 499 占比判断是否多为「后端慢导致前端先断」。
连接数满、文件描述符不足
现象:error.log 出现 "too many open files" 或 "worker_connections are not enough";并发高时新连接失败。
原因 :单机连接数超过 worker_connections × worker_processes,或进程的 fd 限制低于实际需求。
解决方案:
nginx
# nginx.conf 顶层
worker_processes auto;
worker_rlimit_nofile 65535; # 每个 worker 能打开的 fd 上限,需 ≥ worker_connections
events {
worker_connections 10240; # 单 worker 最大并发连接数
use epoll;
multi_accept on;
}
系统级限制需同步放大(否则 worker_rlimit_nofile 受限于 ulimit):
bash
# 临时
ulimit -n 65535
# 持久(示例:systemd 服务)
# 在 /etc/systemd/system/nginx.service.d/override.conf 或 unit 里加:
# LimitNOFILE=65535
响应慢、缓冲与缓冲区
现象:后端很快返回,但用户感觉慢;或大响应时出现不完整、超时。
可能原因 :Nginx 默认会缓冲后端响应,若 proxy_buffers 过小或关闭缓冲,大响应会阻塞或多次读;或后端本身慢。
可调配置:
nginx
location / {
proxy_pass http://tomcat_cluster;
# 缓冲:先接后端数据再发给客户端,减轻后端「写慢」的影响
proxy_buffering on;
proxy_buffer_size 4k; # 响应头缓冲
proxy_buffers 8 8k; # 响应体缓冲块数与大小
proxy_busy_buffers_size 16k; # 正在往客户端发时占用的缓冲
proxy_max_temp_file_size 256m; # 缓冲不够时写临时文件的上限
# 若希望尽快把后端数据流式传给客户端(如大文件下载),可关缓冲
# proxy_buffering off;
}
为什么默认开缓冲? 后端先写完,Nginx 再统一发给客户端,能复用连接、减少后端占用时间;流式场景再按 location 关缓冲。
日志与排查配置(便于线上排错)
在 access_log 中记录上游与耗时,便于查 502/504 和慢请求:
nginx
# 在 http 或 server 中定义 log_format
log_format main_with_upstream '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'upstream: $upstream_addr '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
access_log /var/log/nginx/tomcat_access.log main_with_upstream;
$upstream_addr:实际转发的后端地址,502 时看是否为空或异常。$request_time:请求在 Nginx 内总耗时。$upstream_response_time:后端响应耗时,用于判断慢在 Nginx 还是后端。
排查 502 时 :grep 502 /var/log/nginx/tomcat_access.log 看对应 upstream_addr;同时看 error.log 的 "upstream" 与 "connect() failed"。
限流与简单防护(避免打满后端)
现象:突发流量或恶意请求把 Tomcat 打满,需要在前端限流。
示例配置:
nginx
# 在 http 中定义限流区
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s; # 每 IP 每秒 10 请求
limit_conn_zone $binary_remote_addr zone=addr:10m; # 每 IP 并发连接数
server {
location / {
limit_req zone=one burst=20 nodelay; # 超过 rate 的请求可排队 burst 个,其余 503
limit_conn addr 20; # 每 IP 最多 20 个并发连接
proxy_pass http://tomcat_cluster;
}
}
为什么用 limit_req + limit_conn? limit_req 控制请求速率,limit_conn 控制单 IP 并发连接数,两者结合可减轻突发与单点滥用对后端的影响。
小结(线上问题处理顺序)
- 看状态码与 Nginx/Tomcat 日志:502 先看 upstream 是否可达、max_fails;504 看 proxy_read_timeout 与后端耗时。
- 调超时与缓冲:按业务区分 location 设置 read_timeout;大响应或流式按需调 proxy_buffers 或关缓冲。
- 控连接与限流:worker_rlimit_nofile、worker_connections、limit_req/limit_conn 防止连接与请求打满。
- 上传与大小:413 时调大 client_max_body_size;499 结合日志与后端耗时优化。
💡 最佳实践
配置建议
| 建议 | 说明 | 为什么? |
|---|---|---|
| 动静分离 | Nginx 处理静态,Tomcat 处理动态 | 为什么?静态走 Nginx 省 Tomcat 线程和 CPU,整体 QPS 更高 |
| 会话保持 | 根据应用选 ip_hash、sticky 或 Session 复制 | 为什么?有状态应用必须保证同一用户落到同一台或 Session 共享,否则登录态丢失 |
| 负载均衡 | 多台 Tomcat 分担压力 | 为什么?单机有上限,多机 + 负载均衡才能提高可用性和吞吐 |
| 缓存策略 | 静态设较长 expires,动态 no-cache | 为什么?减少重复请求和后端压力;动态内容缓存会看到旧数据 |
| 日志轮转 | logrotate 或 Nginx 的 access_log 按天/按大小切 | 为什么?单文件过大会影响写性能和排查,轮转后便于按日期查看和归档 |
安全建议
nginx
# 隐藏后端服务器版本
proxy_hide_header X-Powered-By;
# 限制请求大小
client_max_body_size 20M;
# 限制请求方法
if ($request_method !~ ^(GET|HEAD|POST)$ ) {
return 405;
}
# 限制访问
allow 192.168.1.0/24;
deny all;
🔀 与其他软件配合使用及方案对比
实际项目中 Nginx 与 Tomcat 常与数据库、缓存、消息队列、高可用组件等一起使用;同时,前端代理或 Java 容器也有多种替代方案。本节先介绍常见组合架构 ,再对同类方案做简要对比,便于选型与扩展。
与其他软件配合使用
典型组合架构示意
💾 数据与中间件
📦 应用层
🖥️ 接入层
👤 用户
浏览器/客户端
Nginx
反向代理/静态
Tomcat 1
Tomcat 2
Redis
Session/缓存
MySQL
主库
MQ
RabbitMQ/Kafka
常见组合与用途
| 组合 | 角色 | 典型用途 | 为什么这样配? |
|---|---|---|---|
| Nginx + Tomcat + MySQL | Nginx 代理与静态,Tomcat 跑 Java,MySQL 存业务数据 | 经典 Web 三层:展示→应用→数据 | 为什么加 MySQL?Tomcat 只负责计算,持久化交给数据库;Nginx 不直连 MySQL,安全与职责清晰 |
| Nginx + Tomcat + Redis | Redis 做 Session 共享或页面/接口缓存 | 多机 Tomcat 时 Session 共享;热点数据缓存 | 为什么用 Redis?多台 Tomcat 无状态扩展时,Session 存 Redis 比 ip_hash 或 Tomcat 集群复制简单;缓存可减轻 DB 压力 |
| Nginx + Tomcat + MySQL + Redis | 上述两者结合 | 生产常见:静态→应用→缓存→数据库 | 为什么四件套?动静分离、应用水平扩展、缓存降 DB 负载、数据库做主存储,是通用 Web 架构 |
| Nginx + Tomcat + 消息队列(RabbitMQ/Kafka) | 请求进 Tomcat,耗时或解耦任务投递 MQ,异步消费 | 下单写库+发消息、日志/事件采集 | 为什么加 MQ?同步写库+发邮件/短信会拖慢响应;MQ 解耦、削峰,Tomcat 只负责发消息,消费者另起服务 |
| Nginx + Tomcat + Keepalived/LVS | Nginx 或 LVS 前再加一层 VIP,做高可用 | 单 Nginx 故障时自动切到备机 | 为什么需要?Nginx 是单点,配合 Keepalived 双机或 LVS 多机,实现入口高可用 |
| Nginx + Tomcat + ELK/日志中心 | Nginx、Tomcat 日志统一采集到 Elasticsearch,Kibana 查询 | 统一日志、排查问题、审计 | 为什么集中日志?多机多实例时日志分散,集中后便于检索和监控告警 |
| Nginx + Tomcat + Docker/K8s | 容器化部署,Nginx 做 Ingress 或侧车,Tomcat 跑在 Pod 里 | 弹性伸缩、灰度、环境一致 | 为什么容器化?镜像一次构建多环境复用;K8s 可自动扩缩容、滚动发布,Nginx 或 Ingress 做流量入口 |
生活类比:Nginx = 商场大门与导购,Tomcat = 各专柜,MySQL = 仓库账本,Redis = 前台暂存柜,MQ = 内部传话员,Keepalived = 双大门轮流值班。
方案对比:谁来做 Tomcat 的前端代理?
| 对比项 | Nginx + Tomcat | Apache + Tomcat(mod_jk / mod_proxy_ajp) | Caddy + Tomcat | OpenResty + Tomcat |
|---|---|---|---|---|
| 协议 | HTTP 代理(proxy_pass) | AJP 或 HTTP(mod_jk 用 AJP,mod_proxy 可 HTTP/AJP) | HTTP 反向代理 | HTTP 反向代理(可嵌 Lua) |
| 静态能力 | 很强,事件驱动 | 中等,多进程/线程 | 较好,Go 实现 | 同 Nginx,且可 Lua 动态处理 |
| 配置方式 | 手写 conf,语法简洁 | 手写 conf,模块多 | Caddyfile 自动 HTTPS | 同 Nginx + Lua 脚本 |
| AJP 支持 | 需第三方模块,一般不推荐 | 原生 mod_jk / mod_proxy_ajp | 无 | 需第三方或自写 Lua |
| 适用场景 | 通用反向代理 + 负载均衡,静态多 | 历史 Apache 栈、需 AJP 时 | 要自动 HTTPS、配置极简 | 要复杂逻辑、动态路由、WAF 等 |
| 生活类比 | 专业前台 + 多柜台 | 全能前台(也做静态)+ 多柜台 | 自动办卡的前台 | 会编程的前台,规则可自己写 |
为什么 Nginx 不官方支持 AJP? Nginx 以事件驱动、HTTP 为中心设计;AJP 是二进制协议且多与 Apache 生态绑定,社区版一直用 HTTP 连 Tomcat 即可满足大多数场景,性能足够。若必须用 AJP,可选 Apache + mod_jk 或 Nginx 第三方 AJP 模块(需自行评估维护成本)。
方案对比:Java 应用服务器 / Servlet 容器
| 对比项 | Tomcat | Jetty | Undertow |
|---|---|---|---|
| 定位 | Servlet/JSP 容器,实现 Jakarta EE 部分规范 | 轻量嵌入式,常内嵌到应用 | 轻量、高性能,内嵌或独立 |
| 典型用法 | 独立部署,Nginx 反向代理多实例 | 打成 jar 内嵌 Jetty 启动;或独立 | Spring Boot 默认内嵌之一;或独立 |
| 性能/资源 | 稳定、功能全,资源中等 | 轻量、启动快 | 高并发、低内存,性能指标好 |
| 适用 | 传统 Java Web 项目、多应用同机 | 微服务、嵌入式、云原生 | 高并发 API、微服务、与 Spring Boot 搭配 |
为什么很多项目仍选 Tomcat? 生态成熟、文档多、与 Spring 等框架兼容好,独立部署时运维熟悉度高;Jetty/Undertow 更常见于「内嵌」或对资源/延迟极敏感的场景。
小结(为什么了解配合与对比有用?)
- 配合使用:按业务加 MySQL、Redis、MQ、Keepalived、日志中心或容器平台,可逐步从「Nginx+Tomcat」扩展为完整生产架构。
- 前端代理对比:要简单稳定、静态多选 Nginx;历史 Apache 栈或必须 AJP 选 Apache+mod_jk;要自动 HTTPS 和极简配置选 Caddy;要复杂逻辑选 OpenResty。
- Java 容器对比:独立部署、多应用选 Tomcat;内嵌、微服务可考虑 Jetty/Undertow。
🎯 总结与官方参考
Nginx + Tomcat 是经典的反向代理架构组合,发挥各自优势,实现高性能的 Web 服务。
核心要点记忆口诀:
- Nginx 做代理,负载均衡能力强
- Tomcat 做 Java,动态应用它最行
- 动静要分离,静态 Nginx 处理
- 会话需保持,ip_hash 或 sticky
- 缓存要合理,过期时间要设置
生活类比总结:
- Nginx = 智能引导员,分发客流
- Tomcat = 专柜服务员,提供专业服务
- 负载均衡 = 根据客流量分配柜台
- 缓存 = 把常用商品放在前台
最后提醒:Nginx 和 Tomcat 各有优势,合理配置才能发挥最大效能!
📋 配置项说明(含义与用途,以官网为准)
以下列出文档中出现的 Nginx、Tomcat 配置项,并给出含义 与典型用途 ,便于查阅与调优。Nginx 以 ngx_http_proxy_module、ngx_http_upstream_module、ngx_http_core_module 为准;Tomcat 以 HTTP Connector 为准。
Nginx 主进程与 events
| 配置项 | 含义(官方) | 用途 / 建议 |
|---|---|---|
| user | 运行 worker 进程的用户(及可选的组) | 不用 root 跑 worker,安全;如 nginx |
| worker_processes | worker 进程数量 | auto 一般等于 CPU 核数,充分利用多核 |
| worker_rlimit_nofile | 每个进程可打开的文件描述符上限 | 高并发时需足够大(如 65535),否则 "too many open files" |
| worker_connections | 每个 worker 可同时处理的最大连接数 | 总并发约等于 worker_processes × worker_connections;按预期 QPS 与长连接估算 |
| use | 指定连接复用方法(如 epoll、kqueue) | Linux 下 epoll,高并发必备 |
| multi_accept | 是否一次 accept 多个新连接 | on 可减少唤醒次数,建议开启 |
示例(主进程 + events 写在一起):
nginx
user nginx;
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 10240;
use epoll;
multi_accept on;
}
Nginx http 核心(常用)
| 配置项 | 含义(官方) | 用途 / 建议 |
|---|---|---|
| sendfile | 是否使用内核 sendfile 直接在内核态把文件写到 socket | on 时静态文件少一次用户态拷贝,性能更好 |
| tcp_nopush | 与 sendfile 配合,在包填满或最后一块时再发 | 减少小包数量,适合大文件;与 tcp_nodelay 互斥语义(针对不同场景) |
| tcp_nodelay | 是否禁用 Nagle(小包立即发) | 动态、小响应时 on,降低延迟 |
| keepalive_timeout | 与客户端的 keep-alive 超时时间(秒) | 长连接场景可适当加大(如 65) |
| types_hash_max_size | 影响 MIME 类型哈希表大小 | 虚拟主机很多时可增大,避免 hash bucket size 告警 |
| client_max_body_size | 允许的客户端请求体最大大小(k/m/g) | 上传场景必须设(如 50m),默认 1m 易触发 413 |
| server_tokens | 是否在响应头中输出 Nginx 版本 | off 隐藏版本,减少信息暴露 |
| listen | 监听地址与端口 | 如 80、443 ssl、[::]:80 |
| server_name | 虚拟主机名,用于匹配 Host 头 | 域名或 _ 默认 |
| location | 按 URI 匹配并应用一组指令 | 如 location /、`location ~* .(jpg |
| root / alias | 请求对应到文件系统的路径 | root 把 location 拼到路径后;alias 替换 location 部分 |
| expires | 响应头 Expires / Cache-Control 的缓存时间 | 如 30d、1y,静态资源加速 |
| add_header | 给响应添加自定义头 | 如 Cache-Control、X-Frame-Options |
| access_log | 访问日志路径及可选 format | 如 access_log /var/log/nginx/access.log main; 或 off 关闭 |
| log_format | 定义日志格式名称与格式串 | 可包含 $upstream_addr、$request_time、$upstream_response_time 等便于排错 |
| stub_status | 是否在该 location 输出简单状态页 | 需编译 --with-http_stub_status_module;用于监控 active/reading/writing/waiting 连接数 |
| allow / deny | 按 IP/网段允许或拒绝访问 | 如 allow 192.168.1.0/24; deny all; 限制管理或状态页访问 |
示例(一个 server:静态站 + 状态页只允许内网):
nginx
server {
listen 80;
server_name www.example.com;
# 静态资源:路径用 root,缓存 30 天
location /static/ {
root /var/www;
expires 30d;
add_header Cache-Control "public, immutable";
}
# 状态页只允许内网访问
location /nginx_status {
stub_status on;
access_log off;
allow 192.168.1.0/24;
deny all;
}
access_log /var/log/nginx/access.log;
}
Nginx Gzip
| 配置项 | 含义(官方) | 用途 / 建议 |
|---|---|---|
| gzip | 是否启用 gzip 压缩 | on 减小文本类响应体积,节省带宽 |
| gzip_vary | 是否在响应中加 Vary: Accept-Encoding |
on 便于正确缓存压缩/未压缩版本 |
| gzip_min_length | 仅当响应长度不小于该值(字节)时才压缩 | 太小的响应压缩后可能更大,一般 1024 或 2048 |
| gzip_types | 对哪些 MIME 类型做 gzip | 默认仅 text/html;可加 text/css、application/json、application/javascript 等 |
示例(在 http 里写,对所有 server 生效):
nginx
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
Nginx upstream(官方 ngx_http_upstream_module)
| 配置项 | 含义(官方) | 用途 / 建议 |
|---|---|---|
| upstream name | 定义一组后端服务器,供 proxy_pass 等引用 | 集中管理后端列表与负载策略 |
| server address [parameters] | 组内一台服务器的地址及参数 | 如 ip:port;可带 weight、backup、max_fails、fail_timeout |
| weight | 该服务器的权重,默认 1 | 轮询时按权重分配,机器性能不一时可设不同值 |
| backup | 标记为备份服务器 | 仅当主服务器都不可用时才使用 |
| max_fails | 在 fail_timeout 内连续失败多少次则视为不可用 | 默认 1;一般 2~3,避免网络抖动误判 |
| fail_timeout | 判定失败的时间窗口,以及视为不可用的持续时间(秒) | 该时间内连续 max_fails 次失败则摘除,过段时间再试 |
| keepalive | 与上游保持的空闲长连接数(每 worker) | 需配合 proxy_http_version 1.1 与 proxy_set_header Connection "";减少建连开销 |
| ip_hash | 按客户端 IP 哈希选服务器 | 会话保持;同一 IP 尽量到同一后端 |
| least_conn | 选当前活跃连接数最少的服务器 | 适合请求耗时差异大的场景 |
示例(三台 Tomcat:两台主、一台备份;主节点 2 次失败就摘 30 秒):
nginx
upstream tomcat_cluster {
server 192.168.1.20:8080 weight=1 max_fails=2 fail_timeout=30s;
server 192.168.1.21:8080 weight=1 max_fails=2 fail_timeout=30s;
server 192.168.1.22:8080 backup;
keepalive 32;
}
若要同一用户固定到同一台(会话保持),可改为:
nginx
upstream tomcat_cluster {
ip_hash;
server 192.168.1.20:8080;
server 192.168.1.21:8080;
}
Nginx proxy(官方 ngx_http_proxy_module)
| 配置项 | 含义(官方) | 用途 / 建议 |
|---|---|---|
| proxy_pass | 将请求转发到指定 URI 或 upstream | 如 http://upstream_name 或 http://ip:port/path/;尾有无斜杠影响 URI 是否被替换 |
| proxy_set_header | 设置转发给上游的请求头 | 常用 Host、X-Real-IP、X-Forwarded-For、X-Forwarded-Proto,后端需真实客户端信息时必设 |
| proxy_connect_timeout | 与上游建立连接的超时时间 | 不宜过长,建议 5~60s |
| proxy_send_timeout | 向上游发送请求的超时时间 | 一般与 read 同量级或略小 |
| proxy_read_timeout | 从上游读取响应的超时时间 | 动态接口耗时长可设大(如 120s),静态可小 |
| proxy_http_version | 与上游通信使用的 HTTP 版本 | 使用 upstream keepalive 时须为 1.1(或 2) |
| proxy_set_header Connection "" | 清空 Connection 头 | 与 proxy_http_version 1.1、upstream keepalive 配合 |
| proxy_buffering | 是否缓冲上游响应 | on 时先收完再发给客户端,减轻上游占用;流式可 off |
| proxy_buffer_size | 存放上游响应头的缓冲区大小 | 一般 4k~8k 即可 |
| proxy_buffers | 存放上游响应体的缓冲区数量与大小 | 如 8 4k、8 8k;大响应或高并发可适当加大 |
| proxy_busy_buffers_size | 正在向客户端发送时,可占用的缓冲总大小 | 默认约两倍 proxy_buffer_size;大响应时可增大 |
| proxy_max_temp_file_size | 缓冲不够时写临时文件的最大大小 | 大响应时避免占满内存,可设 256m 等 |
| proxy_request_buffering | 是否先缓冲客户端请求体再转给上游 | on 时上游更稳定;大上传有时关掉以流式转发 |
| proxy_hide_header | 不把上游某响应头传给客户端 | 如隐藏 X-Powered-By |
| proxy_redirect | 重写上游返回的 Location、Refresh 头中的 URL | 上游是内网地址时,可改为对外域名,避免客户端直接请求后端 |
示例(把 / 转发到 Tomcat,带真实 IP、超时、缓冲;并隐藏后端版本头):
nginx
location / {
proxy_pass http://tomcat_cluster;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 60s;
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 8k;
proxy_busy_buffers_size 16k;
proxy_hide_header X-Powered-By;
}
proxy_pass 带不带 URI 的区别(容易搞混,看例子):
nginx
# 请求 /api/user → 转给后端的是 /api/user(完整 URI)
location /api/ {
proxy_pass http://backend;
}
# 请求 /api/user → 转给后端的是 /user(把 /api 换成了空)
location /api/ {
proxy_pass http://backend/;
}
Nginx 限流(limit_req / limit_conn)
| 配置项 | 含义(官方) | 用途 / 建议 |
|---|---|---|
| limit_req_zone | 定义限流用的共享内存区与速率(如按 IP) | 如 $binary_remote_addr zone=one:10m rate=10r/s |
| limit_req | 在 location 中应用该 zone,可选 burst、nodelay | burst 为突发排队数;nodelay 表示立即按 rate 处理排队 |
| limit_conn_zone | 定义按 key(如 IP)计连接数的 zone | 与 limit_conn 配合 |
| limit_conn | 同一 key 允许的最大并发连接数 | 防单 IP 占满连接 |
示例(按 IP 限速:每秒 10 请求、突发 20;每 IP 最多 20 个并发连接):
nginx
# 在 http 里定义 zone(按 IP,10m 内存,每秒 10 个请求)
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;
server {
location / {
limit_req zone=one burst=20 nodelay;
limit_conn addr 20;
proxy_pass http://tomcat_cluster;
}
}
Tomcat HTTP Connector 常见属性(官方 HTTP Connector)
| 属性 | 含义(官方) | 用途 / 建议 |
|---|---|---|
| port | Connector 监听的 TCP 端口 | 如 8080(HTTP)、8443(HTTPS) |
| protocol | 协议实现类 | HTTP/1.1(默认,自动选 NIO/NIO2/APR)、AJP/1.3 等 |
| connectionTimeout | 接受连接后,等待请求 URI 行出现的时间(毫秒) | 默认 20000;过小易在慢网络下断连 |
| maxThreads | 处理请求的最大线程数 | 同时处理的请求数上限;按压测与 CPU 调,如 500 |
| minSpareThreads | 始终保持的最小空闲线程数 | 避免突发时临时创建过多线程,如 50 |
| maxSpareThreads | 允许的最大空闲线程数 | 超过则回收;与 minSpareThreads 一起控制线程池形状 |
| acceptCount | 当达到 maxConnections 时,操作系统等待队列的最大长度 | 队列满后新连接可能被拒绝或超时;如 500 |
| acceptorThreadCount | 接受连接的线程数 | 高并发时可适当增加(如 2~4),默认 1 |
| enableLookups | 是否对 getRemoteHost() 做 DNS 反向解析 | 生产建议 false,避免 DNS 延迟 |
| compression | 是否对响应做 GZIP 压缩 | on 节省带宽;也可设为最小字节数(如 2048) |
| compressionMinSize | 响应体至少多少字节才压缩(字节) | 太小压缩收益低甚至变大;默认 2048 |
| compressableMimeType | 对哪些 MIME 类型压缩 | 如 text/html,text/xml,text/plain,text/css,application/json |
| noCompressionUserAgents | 不压缩的 User-Agent 正则 | 某些旧客户端实现有问题,可排除,如 gozilla-trident |
| URIEncoding | 解码 URI 使用的字符集 | 与前端一致,常用 UTF-8 |
| address | 绑定监听的 IP | 多网卡时可指定,如仅内网 192.168.1.20 |
| redirectPort | 需 SSL 时重定向到的端口 | 如 8443 |
| maxConnections | 服务器接受并处理的最大连接数 | 超过后只接受不处理,等空闲;默认 8192 |
示例(server.xml 里一段 HTTP Connector,含线程池、超时、压缩):
xml
<Connector port="8080"
protocol="HTTP/1.1"
connectionTimeout="20000"
maxThreads="500"
minSpareThreads="50"
maxSpareThreads="200"
acceptCount="500"
acceptorThreadCount="2"
enableLookups="false"
compression="on"
compressionMinSize="2048"
compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json"
noCompressionUserAgents="gozilla-trident"
URIEncoding="UTF-8"
redirectPort="8443" />
- compressionMinSize="2048":响应体小于 2KB 不压缩(避免越压越大)。
- noCompressionUserAgents:遇到这些 UA 不压缩,避免老浏览器解压异常。
Tomcat AJP Connector 常见属性(AJP Connector)
| 属性 | 含义(官方) | 用途 / 建议 |
|---|---|---|
| port | AJP 监听端口 | 默认 8009 |
| protocol | AJP/1.3 | 与 Apache/Nginx AJP 模块通信 |
| secret | 与 Web 服务器约定的密钥(可选但推荐) | 防止未授权访问 AJP 端口 |
| secretRequired | 是否强制要求 secret | 生产建议 true |
| address | 绑定 IP | 仅内网时可指定 |
| redirectPort | 需 SSL 时重定向端口 | 如 8443 |
示例(仅内网监听、带密钥,防止未授权连 AJP):
xml
<Connector port="8009"
protocol="AJP/1.3"
secret="your-secret-string"
secretRequired="true"
address="192.168.1.20"
redirectPort="8443" />
- secret:和 Nginx/Apache 里配置的 AJP 密钥一致,否则连不上。
- address="192.168.1.20":只在本机该 IP 上监听,不暴露到公网。
以上配置项均可在官方文档中查到完整语法、默认值与上下文。文档中若还有未列出的指令,以 nginx.org 与 tomcat.apache.org 为准。上面的示例可直接复制到对应文件里,按自己 IP、端口、域名改一改即可用。
📚 官方文档与参考
| 资源 | 链接 | 用途 |
|---|---|---|
| Nginx 反向代理 | Reverse Proxy | proxy_pass、proxy_set_header、超时等 |
| Nginx proxy 模块 | ngx_http_proxy_module | 官方指令列表与参数 |
| Nginx upstream 模块 | ngx_http_upstream_module | upstream、server、负载算法、backup |
| Nginx 负载均衡 | HTTP Load Balancing | 负载均衡配置与健康检查 |
| Tomcat 9 配置 | Apache Tomcat 9 Configuration | server.xml、Connector、Host、Context |
| Tomcat AJP Connector | The AJP Connector | AJP 端口、secret、address 等 |