1. 单机瓶颈是最先遇到的那堵墙
先讲一个我身边发生过的场景。某个业务系统上线半年,功能稳定,代码质量也不算差,部署方式就是一台 8C16G 的云主机上跑一个 Tomcat,前面挂 Nginx,数据库单独一台。刚开始每天几百个请求,Tomcat 轻轻松松。后面前端页面加了实时数据轮询,加上推广渠道带来一批用户,某天下午四点多,运营群里开始有人截图反馈:页面一直在转圈,登录接口要等十几秒才返回。
我登上服务器一看,Tomcat 的 localhost_access_log 里响应时间普遍从几十毫秒飙升到几秒,再 jstack 看线程栈,大量线程都卡在 http-nio-8080-exec-* 的数据库连接等待上。其实当时问题已经不只是 Tomcat 单点瓶颈,还有数据库慢查询、连接池不够、GC 频繁等一系列连锁反应。但作为一个 Java Web 服务,单台 Tomcat 的物理边界就在那里:默认 maxThreads 只有 200,再算上线程切换、JVM 堆内存、GC 停顿,一台机器能扛住的并发是有限的。CPU 加核、内存扩容能缓解,但加到最后成本越来越高,而且机器总有宕机风险。
所以真正的解法是做集群:多台机器上跑多个 Tomcat 实例,前面用负载均衡把流量分发到不同节点,这样单台机器挂了不会直接导致整个应用不可用。但集群部署不是简单地把 Tomcat 复制几份就能跑起来的,做过的人都知道,这里有两个坎必须迈过去:
一个是流量怎么分,也就是负载均衡;另一个更隐蔽——多个 Tomcat 之间怎么保证用户 Session 不丢。用户第一次请求在节点 A 上登录了,Session 存在节点 A 的内存里;下一次请求被 Nginx 分发到节点 B,节点 B 的内存里根本没有这个 Session,应用就会认为用户未登录,于是出现“明明登录了却反复跳回登录页”的诡异现象。
这篇文章就以我实际部署过的方案为主线,从单机到双节点、从负载均衡到 Session 共享,把完整的配置、排错思路和上线验证方法都讲清楚。适合所有正在准备生产环境部署的 Java 后端开发,也适合面试前把“Tomcat 集群”这个知识点真正弄明白的人。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 集群拓扑怎么选:先画一张不绕弯的架构图
在做具体配置之前,先把目标架构定下来。Tomcat 集群部署说起来可以有很多花样,但真正值得长期维护的拓扑并不复杂。我最终搭出来的形态是这样:
客户端请求先到 Nginx(或者云上的负载均衡器),Nginx 按策略把请求转发给后端的多个 Tomcat 节点;这些 Tomcat 节点都不再本地保存用户 Session 的“唯一真身”,而是把 Session 数据统一放到一个独立的 Redis 中。这样任何一个节点都能接住任意用户的请求,因为 Session 数据在共享存储里,而不是在某台 Tomcat 的内存里。
这个拓扑适合大多数业务场景。原因有三点:
第一,Tomcat 节点之间完全对等,没有主从关系。任何一个节点宕机、重启、发新版本,流量都可以被其他节点接住,用户无感知。
第二,水平扩容非常直接。流量涨了,再起一台 Tomcat,加进 Nginx 的 upstream 就行,不需要调整已有节点的任何配置。
第三,应用状态和服务器实例解耦。Session 数据在 Redis 里,Tomcat 实例维护不维护本地状态就不重要了,这对后面做容器化、自动化发布都是很大的优势。
如果是同机做多实例测试,或者只有两台低配机器,拓扑不变,只是 Tomcat 实例可以放在同一台机器上。这种情况下有个细节要特别注意:同一个 Tomcat 安装目录不能直接启动两份,否则端口会冲突。你应该复制出两个独立实例,分别调整三个关键端口:8005 是关闭 Tomcat 的管理端口,8080 是 HTTP 服务端口,8009 是 AJP 端口。同一台机器上两个实例如果都用默认端口,第二个实例肯定启动失败,报 Address already in use。
我一般习惯建两个目录,每个目录放独立的 conf、logs、temp、webapps、work,然后用 CATALINA_BASE 指向各自的目录启动。例如:
bash复制export CATALINA_HOME=/opt/tomcat
export CATALINA_BASE=/opt/tomcat-instance1
$CATALINA_HOME/bin/startup.sh
实例 1 的 server.xml 里改成:
xml复制<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
</Service>
</Server>
实例 2 对应改成 8006/8081/8010,保证不冲突。这样就得到了两个完全独立的 Tomcat 服务。多机部署就不用考虑端口冲突,但生产环境我仍然建议把实例目录和日志目录分开,方便出了问题定位。
3. 负载均衡落地:Nginx upstream 配置与流量验证
负载均衡的选型,现在基本不用纠结。公司有云负载均衡产品就直接用云上的 SLB;没有的话,Nginx 是成本最低、资料最多、最容易排查的选择。本文以 Nginx 为例。
Nginx 的负载均衡核心是 upstream 块。下面是我实际用过的配置,先放出来,再逐个参数解释:
code复制
