Nginx反向代理多Tomcat实例部署实战:从单机多端口到生产环境调优
1. 从单兵作战到集群部署:为什么我们需要多项目同IP不同端口
在项目开发的早期,我们通常会把一个Web应用直接扔进Tomcat的webapps目录,启动服务,通过http://服务器IP:8080就能访问。这就像开一家小店,所有商品都摆在一个货架上,简单直接。但随着业务发展,公司可能同时运营着官网、后台管理系统、API服务、微信小程序等多个独立项目。如果还沿用“一个Tomcat一个应用”的老办法,你会面临几个非常现实的问题:服务器资源浪费(每个Tomcat实例都占用内存和线程)、管理混乱(多个Tomcat进程需要分别监控和启停)、以及端口冲突(默认8080端口只有一个)。
这时,“同一个IP,不同端口”的部署模式就成了一个自然而优雅的解决方案。它允许你在单台服务器上,通过像http://your-server.com:8081、:8082、:8083这样的地址,分别访问不同的项目。其核心价值在于资源隔离与统一管理的平衡。每个项目运行在独立的Tomcat实例中,拥有独立的应用上下文、日志文件和JVM环境,一个项目的崩溃或高负载不会直接影响其他项目。同时,它们又共享同一台服务器的硬件资源,并由你集中管理,极大地提升了资源利用率和运维的便捷性。
然而,直接暴露一堆Tomcat端口给用户并不是一个好主意。端口号不友好、难以记忆,并且Tomcat在处理大量静态资源(如图片、CSS、JS文件)时,性能远不如专业的Web服务器。这就是Nginx登场的时候。Nginx可以作为反向代理和静态资源服务器,对外只暴露80(HTTP)或443(HTTPS)端口,然后根据访问的域名或路径,将请求智能地转发到后台对应的Tomcat端口上。用户访问的是http://app1.your-domain.com,背后实际是http://localhost:8081。这样既隐藏了内部复杂的端口结构,提升了安全性和用户体验,又通过Nginx的高并发处理能力,优化了整体性能。接下来,我将以一个真实的服务器环境为例,手把手带你完成从零开始,部署两个独立Spring Boot项目(项目A和项目B)的全过程。
2. 战场准备:服务器环境与软件基石
在开始部署之前,我们必须确保战场——服务器环境——是干净且准备就绪的。我假设你使用的是一台全新的CentOS 7.x或Ubuntu 20.04 LTS的云服务器。以下所有操作,如无特别说明,均在服务器上通过SSH连接执行。
2.1 基础环境检查与配置
首先,我们需要一个稳定的Java环境来运行Tomcat。我强烈推荐使用Oracle JDK 8或OpenJDK 11/17这些经过长期实践检验的LTS版本。你可以通过以下命令检查并安装:
# 检查是否已安装Java java -version # 如果未安装,以CentOS为例安装OpenJDK 11 sudo yum install -y java-11-openjdk-devel # 配置JAVA_HOME环境变量(通常安装后自动设置,但建议确认) echo $JAVA_HOME # 如果未设置,找到Java安装路径,例如 /usr/lib/jvm/java-11-openjdk-11.0.xx.x-x.el7_9.x86_64 # 编辑 /etc/profile 文件,在末尾添加: # export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-11.0.xx.x-x.el7_9.x86_64 # export PATH=$JAVA_HOME/bin:$PATH # 然后执行 source /etc/profile 使配置生效接下来是防火墙。云服务器通常有安全组和系统防火墙两道关卡。我们必须确保计划使用的端口(例如8081, 8082, 以及Nginx的80端口)是开放的。
# 对于CentOS 7的firewalld sudo firewall-cmd --permanent --add-port=8081/tcp sudo firewall-cmd --permanent --add-port=8082/tcp sudo firewall-cmd --permanent --add-port=80/tcp sudo firewall-cmd --reload # 对于Ubuntu的ufw sudo ufw allow 8081/tcp sudo ufw allow 8082/tcp sudo ufw allow 80/tcp sudo ufw reload同时,别忘了在云服务商的控制台(如阿里云、腾讯云的安全组规则)中,添加入站规则,允许这些端口的流量。
2.2 获取并规划我们的“士兵”:Tomcat与Nginx
我们将采用多个Tomcat实例的方案,而不是在一个Tomcat中部署多个WAR包。这样做隔离性更好,也便于独立升级和维护。规划如下:
- 项目A:使用Tomcat实例1,端口8081。
- 项目B:使用Tomcat实例2,端口8082。
- Nginx:作为统一入口,监听80端口。
首先,下载Tomcat。建议使用Tomcat 9.x版本,它在稳定性和功能上是一个很好的平衡点。避免使用过旧的版本(如7.x)可能存在的安全漏洞,也无需盲目追求最新版。
# 进入一个常用的安装目录,例如 /opt cd /opt # 下载Tomcat 9.0.x 压缩包(请从官网获取最新链接) sudo wget https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.xx/bin/apache-tomcat-9.0.xx.tar.gz # 解压 sudo tar -zxvf apache-tomcat-9.0.xx.tar.gz现在,我们有了一个apache-tomcat-9.0.xx目录。为了创建两个实例,我们直接复制两份。
sudo cp -r apache-tomcat-9.0.xx tomcat-8081 sudo cp -r apache-tomcat-9.0.xx tomcat-8082这样,/opt/tomcat-8081和/opt/tomcat-8082就是两个完全独立的Tomcat服务器目录了。接下来是Nginx,大多数Linux发行版的官方仓库里都有,直接安装即可。
# CentOS sudo yum install -y epel-release sudo yum install -y nginx # Ubuntu sudo apt update sudo apt install -y nginx安装完成后,先不要启动。我们需要对它们进行关键的配置,让它们能够协同工作。
3. 核心配置实战:让每个组件各司其职
配置是部署的灵魂,一个错误的配置可能导致服务无法启动或行为异常。我们按顺序来。
3.1 配置Tomcat实例:修改端口与上下文
两个Tomcat实例不能使用相同的端口,否则会冲突。我们需要修改每个实例的server.xml文件,主要关注三个端口:SHUTDOWN端口、HTTP/1.1连接器端口和AJP连接器端口(如果不用可以注释掉)。
配置Tomcat-8081 (/opt/tomcat-8081/conf/server.xml):找到以下部分并进行修改:
<!-- 关闭端口,用于执行shutdown.sh脚本 --> <Server port="8005" shutdown="SHUTDOWN"> ... <Service name="Catalina"> <!-- 主要的HTTP连接器,我们将它改为8081 --> <Connector port="8081" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> <!-- AJP连接器,通常用于与Apache HTTPD集成,如果只用Nginx,可以注释掉或改端口 --> <!-- <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> --> ... </Service> </Server>同时,为了确保应用上下文路径清晰,我们修改/opt/tomcat-8081/conf/server.xml中的Host标签,添加一个上下文,但这步更常见的做法是直接部署WAR包或使用context.xml。我们稍后在部署应用时再细说。
配置Tomcat-8082 (/opt/tomcat-8082/conf/server.xml):同理,将Server端口改为8006(避免冲突),Connector端口改为8082。
<Server port="8006" shutdown="SHUTDOWN"> ... <Connector port="8082" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> ... </Server>注意:在生产环境中,仅仅修改端口是不够的。你还需要关注Tomcat的内存设置(
JAVA_OPTS),可以在/opt/tomcat-8081/bin/catalina.sh(Linux)或catalina.bat(Windows)的开头添加,例如export JAVA_OPTS="-Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m",根据你的服务器内存和应用需求调整。否则,默认内存可能太小,导致应用在运行一段时间后内存溢出。
3.2 准备并部署Web应用
假设你的两个项目都是Spring Boot项目,并且已经打包成了可执行的JAR文件(app-a.jar和app-b.jar)。对于Tomcat部署,我们需要的是WAR包。如果你用的是Spring Boot内嵌Tomcat,可以通过Maven或Gradle打包时指定打包方式为war,或者使用一个“瘦身”的WAR包部署到外部Tomcat。
这里我演示更通用的方式:将Spring Boot可执行JAR作为独立应用运行,但通过Tomcat来代理。不过,更传统的做法是生成WAR包。假设我们已经有了project-a.war和project-b.war。
# 将WAR包分别放入两个Tomcat的webapps目录 sudo cp project-a.war /opt/tomcat-8081/webapps/ # 默认情况下,Tomcat会自动解压WAR包,应用上下文路径就是文件名(project-a) # 如果你想指定根路径访问(即通过 http://ip:8081/ 直接访问),可以把WAR包重命名为ROOT.war # sudo cp project-a.war /opt/tomcat-8081/webapps/ROOT.war sudo cp project-b.war /opt/tomcat-8082/webapps/部署后,启动Tomcat进行测试:
# 启动 sudo /opt/tomcat-8081/bin/startup.sh sudo /opt/tomcat-8082/bin/startup.sh # 查看日志,确认启动成功 tail -f /opt/tomcat-8081/logs/catalina.out tail -f /opt/tomcat-8082/logs/catalina.out如果看到类似Server startup in [xxxx] milliseconds的日志,说明启动成功。此时,你应该能通过http://你的服务器IP:8081/project-a和http://你的服务器IP:8082/project-b访问到你的应用了。
3.3 配置Nginx反向代理:统一入口与负载分发
现在,两个Tomcat实例已经各自为战。我们需要Nginx作为总指挥,将外部的请求按照规则分发给它们。Nginx的核心配置文件是/etc/nginx/nginx.conf,但通常我们会在/etc/nginx/conf.d/目录下为每个站点创建独立的配置文件,这样更清晰。
首先,备份默认配置,然后创建我们的代理配置:
sudo mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.bak sudo vi /etc/nginx/conf.d/multi-tomcat.conf在multi-tomcat.conf文件中,我们写入以下配置。这里演示两种常见的路由方式:基于端口的反向代理和基于子域名的反向代理。
方案一:基于路径区分(适用于没有独立域名的情况)假设我们通过不同的URL路径来访问不同项目:/app/转发给项目A,/admin/转发给项目B。
server { listen 80; # 监听80端口 server_name your-server-ip-or-domain; # 你的服务器IP或域名 # 代理到项目A (Tomcat 8081) location /app/ { # 注意结尾的斜杠,非常重要! proxy_pass http://127.0.0.1:8081/project-a/; 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_redirect /project-a/ /app/; } # 代理到项目B (Tomcat 8082) location /admin/ { proxy_pass http://127.0.0.1:8082/project-b/; 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_redirect /project-b/ /admin/; } # 静态资源可以直接由Nginx处理,提升性能 location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ { # 这里可以指向一个统一的静态资源目录,或者由各自的应用处理 # 例如:root /opt/static; expires 7d; # 设置浏览器缓存7天 add_header Cache-Control "public, immutable"; } }方案二:基于子域名区分(更清晰,更推荐)如果你有两个域名,例如app.example.com和admin.example.com,配置会更简洁。
# 项目A的配置 server { listen 80; server_name app.example.com; location / { proxy_pass http://127.0.0.1:8081; # 如果项目A部署为ROOT,直接转发到端口 # 或者 proxy_pass http://127.0.0.1:8081/project-a/; 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; } } # 项目B的配置 server { listen 80; server_name admin.example.com; location / { proxy_pass http://127.0.0.1:8082; 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; } }配置完成后,检查Nginx配置语法是否正确,然后重启Nginx服务。
sudo nginx -t # 测试配置,看到 `syntax is ok` 和 `test is successful` 表示成功 sudo systemctl restart nginx # 重启Nginx服务现在,当你访问http://app.example.com时,Nginx会将请求转发到本机的8081端口,由Tomcat实例1处理项目A;访问http://admin.example.com则转发到8082端口,由Tomcat实例2处理项目B。对用户而言,他们完全感知不到后端端口的差异。
4. 深度调优与生产环境加固
基础部署完成后,系统可以运行,但距离一个稳定、高效的生产环境还有距离。以下几个方面的调优至关重要。
4.1 Tomcat性能参数调优
默认的Tomcat配置是为开发环境设计的,并发能力很弱。主要调整/opt/tomcat-8081/conf/server.xml中的Connector配置。
<Connector port="8081" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" maxThreads="200" <!-- 最大线程数,根据服务器CPU核心数调整,建议200-500 --> minSpareThreads="20" <!-- 最小空闲线程 --> acceptCount="100" <!-- 等待队列长度,当所有线程都在忙时,新请求在此排队 --> maxConnections="10000" <!-- 最大连接数 --> compression="on" <!-- 开启GZIP压缩,减少传输体积 --> compressionMinSize="1024" compressableMavenType="text/html,text/xml,text/css,text/javascript,application/json" />- maxThreads:这是最重要的参数。设置过低,并发请求来了没线程处理;设置过高,线程上下文切换开销巨大。一个经验公式是
CPU核心数 * (1 + 平均等待时间/平均计算时间)。对于IO密集型的Web应用,可以设高一些,比如200-400。 - acceptCount:当所有工作线程都在忙时,新来的请求会进入等待队列。这个值设得太小,高并发时会有大量连接被直接拒绝(返回Connection refused);设得太大,排队请求过多会导致响应时间剧增。通常设置为
maxThreads的一半到相等。 - JVM参数:如前所述,在
catalina.sh中设置JAVA_OPTS。对于生产环境,除了堆内存,还要关注GC策略。例如使用G1垃圾收集器:-XX:+UseG1GC -XX:MaxGCPauseMillis=200。
4.2 Nginx反向代理高级配置
基础的proxy_pass只是完成了转发,生产环境还需要考虑超时、缓冲、头部信息传递等问题。
location / { proxy_pass http://127.0.0.1:8081; 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_connect_timeout 30s; # 与后端服务器建立连接的超时时间 proxy_send_timeout 60s; # 向后端服务器发送请求的超时时间 proxy_read_timeout 60s; # 从后端服务器读取响应的超时时间 # 启用缓冲,缓解后端压力 proxy_buffering on; proxy_buffer_size 4k; proxy_buffers 8 4k; proxy_busy_buffers_size 8k; # 如果后端服务器需要获取用户真实IP,这个很重要 # 同时,如果应用内有生成绝对URL(如重定向),需要正确设置scheme proxy_set_header X-Forwarded-Host $server_name; }proxy_read_timeout尤其重要,如果你的应用有长时间处理的接口(如文件上传、大数据导出),需要将这个值调大,否则Nginx会在超时后向客户端返回502错误。
4.3 日志集中管理与切割
默认情况下,Tomcat和Nginx的日志都写在各自的目录下,日积月累会撑满磁盘,也不利于排查问题。我们需要配置日志切割。
对于Tomcat:可以继续使用自带的catalina.out,但配合logrotate进行切割。在/etc/logrotate.d/下创建文件tomcat-8081:
/opt/tomcat-8081/logs/catalina.out { daily rotate 30 copytruncate missingok compress delaycompress notifempty dateext }copytruncate是关键,它先复制日志文件,然后清空原文件,避免重启Tomcat服务。
对于Nginx:Nginx的访问日志和错误日志本身支持按天切割,但需要配置。更常见的做法是使用logrotate。编辑/etc/logrotate.d/nginx,确保配置合理。
更进阶的做法是搭建一个集中的日志收集系统,比如使用ELK Stack(Elasticsearch, Logstash, Kibana)或EFK Stack(Fluentd替代Logstash),将所有服务器的日志统一收集、索引和可视化展示,这对于多服务器、多应用的环境是必不可少的。
4.4 使用Systemd管理服务
使用startup.sh和shutdown.sh管理Tomcat不够规范,也不利于服务自启。我们可以为每个Tomcat实例创建systemd服务单元文件。
创建文件/etc/systemd/system/tomcat-8081.service:
[Unit] Description=Apache Tomcat 9 Instance for Port 8081 After=network.target [Service] Type=forking User=tomcat # 建议创建一个专门的tomcat用户来运行,提升安全性 Group=tomcat Environment=JAVA_HOME=/usr/lib/jvm/java-11-openjdk Environment=CATALINA_PID=/opt/tomcat-8081/temp/tomcat.pid Environment=CATALINA_HOME=/opt/tomcat-8081 Environment=CATALINA_BASE=/opt/tomcat-8081 Environment='CATALINA_OPTS=-Xms512m -Xmx1024m -server -XX:+UseG1GC' Environment='JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom' ExecStart=/opt/tomcat-8081/bin/startup.sh ExecStop=/opt/tomcat-8081/bin/shutdown.sh RestartSec=10 Restart=always [Install] WantedBy=multi-user.target然后执行:
sudo systemctl daemon-reload sudo systemctl start tomcat-8081 sudo systemctl enable tomcat-8081 # 设置开机自启同样地为tomcat-8082创建服务文件。对于Nginx,安装时通常已经创建了systemd服务(nginx.service),直接使用systemctl管理即可。
5. 故障排查与日常维护指南
部署上线只是开始,运维过程中总会遇到各种问题。掌握一套排查方法比记住所有答案更重要。
5.1 经典问题排查链路
当你通过Nginx访问应用出现502 Bad Gateway或504 Gateway Timeout时,不要慌,按照以下链路自上而下排查:
检查Nginx状态与配置:
sudo systemctl status nginx # 查看Nginx是否在运行 sudo nginx -t # 确认配置文件语法无误 sudo tail -f /var/log/nginx/error.log # 查看Nginx错误日志,这里常有关键线索如果错误日志显示
connect() failed (111: Connection refused) while connecting to upstream,说明Nginx无法连接到后端的Tomcat,转到第2步。检查Tomcat状态与端口:
sudo systemctl status tomcat-8081 # 查看Tomcat服务状态 sudo netstat -tlnp | grep :8081 # 确认8081端口是否在监听 sudo tail -f /opt/tomcat-8081/logs/catalina.out # 查看Tomcat启动日志,看是否有异常 sudo tail -f /opt/tomcat-8081/logs/localhost.yyyy-mm-dd.log # 查看应用日志如果Tomcat没启动,查看日志中的异常信息。常见原因有:端口被占用、JVM内存不足、应用依赖的数据库或Redis连接不上、WAR包损坏等。
检查应用本身:如果Tomcat已启动且端口监听正常,但Nginx还是报错,可能是应用内部问题。查看应用特定的日志文件(如Spring Boot的
application.log或通过Logback配置的日志),看请求是否到达了应用,应用是否抛出了未处理的异常。检查防火墙与安全组:确认服务器的本地防火墙(firewalld/ufw)和云平台的安全组规则,是否允许了80、8081、8082端口的入站流量。一个我踩过的坑:有一次配置全对,但就是访问不了,最后发现是云服务商的安全组规则只开了22(SSH)端口,忘了开80和808x端口。
5.2 日志分析与性能监控
Nginx访问日志分析:
/var/log/nginx/access.log记录了所有请求。你可以使用awk、grep等命令进行简单分析,例如查看PV、UV、最频繁访问的URL、响应时间最长的接口等。对于更复杂的分析,推荐使用GoAccess这类实时日志分析工具。# 统计状态码分布 awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn # 统计最耗时的请求(假设日志格式中$request_time在倒数第4列) awk '{print $(NF-3), $7}' /var/log/nginx/access.log | sort -rn | head -20监控Tomcat状态:Tomcat自带了管理界面,但生产环境不建议开启,存在安全风险。可以通过JMX(Java Management Extensions)进行监控。在Tomcat的
catalina.sh的JAVA_OPTS中添加JMX参数,然后使用JConsole、VisualVM或Prometheus + JMX Exporter来收集JVM内存、线程、GC情况以及Tomcat的连接池、请求处理数量等指标。系统级监控:使用
top、htop、vmstat、iostat等命令监控服务器的CPU、内存、磁盘IO和网络流量。设置报警阈值,当资源使用率持续过高时及时预警。
5.3 安全加固建议
- 最小权限原则:不要使用root用户运行Tomcat和Nginx。创建专门的系统用户(如
tomcat、nginx),并限制其权限。 - 隐藏版本信息:在Nginx配置中,可以添加
server_tokens off;来隐藏Nginx版本号。在Tomcat的server.xml中,可以修改Connector的server属性来隐藏Tomcat版本信息。 - HTTPS化:使用Let‘s Encrypt等免费证书为你的域名启用HTTPS。在Nginx配置中监听443端口,并配置SSL证书和强制HTTP跳转HTTPS。
- 定期更新:关注Tomcat、Nginx、JDK以及操作系统安全公告,定期更新补丁。
- 关闭不必要的服务:如Tomcat的AJP连接器(如果不用)、管理后台;Nginx的
autoindex模块等。
部署和维护这样一个多项目环境,就像管理一支小型部队。清晰的架构规划(Nginx作指挥,多个Tomcat作士兵),细致的配置(端口、代理规则、性能参数),加上完善的监控和排错流程,是保证其稳定高效运行的关键。这套方案不仅适用于文中演示的两个项目,完全可以扩展到三个、五个甚至更多。关键在于保持配置的规范性和一致性,为每个“士兵”建立独立的目录、服务单元和日志,这样在需要扩容或故障替换时,你才能游刃有余。
