Tomcat 集群实战:Nginx 负载均衡、Redis 会话共享与故障排查
Tomcat 集群实战:Nginx 负载均衡、Redis 会话共享与故障排查
单台 Tomcat 上线第一周就暴露了问题:一次发版重启,业务断了十几分钟;访问量一上来,CPU 直接顶到 100%。趁着业务还在爬坡期,把架构从单节点扩成了两节点集群——过程里踩的坑,比配置本身值得记录得多。
一、从单机到两节点
目标拓扑很常规:
1 | 用户 → Nginx(lb01) → upstream → web01 / web02 (Tomcat) |
新节点的部署不是重装一遍,而是从老节点复制(前提是上一步的目录规范做得好):
1 | # web01 上把程序目录和 systemd 配置推到 web02 |
静态资源要共享,不然图片”时有时无”
博客系统有上传目录(附件、头像),如果两个节点各存各的,用户刷新一下换到另一台机器,图片就 404。解决办法是所有节点挂载同一个 NFS 目录:
1 | # NFS 服务端 |
注意 anonuid/anongid=666 要跟 Web 运行用户的 uid/gid 对上,否则写文件会报权限拒绝——这是挂载 NFS 共享上传目录最常见的坑。
Nginx 侧接入
1 | # /etc/nginx/conf.d/proxy_zrlog.conf |
proxy_params 是我单独维护的公共代理参数文件,所有代理站点统一 include:
1 | proxy_set_header Host $http_host; |
有个细节值得说:Nginx 默认会把请求头里的 Host 改写成 $proxy_host(也就是 upstream 里写的名字或 IP)。所以不管后端是 Tomcat 还是 Nginx,只要不显式透传,后端日志里看到的 Host 都可能不是用户实际访问的域名。想让后端保留真实域名,必须显式配置 proxy_set_header Host $http_host——这也是 proxy_params 第一行的由来。
二、HTTPS 接入:证书放在哪一层
证书统一放在负载均衡层,后端继续跑 HTTP,这样证书只维护一份:
1 | server { |
三、会话保持:轮询之后,用户开始”随机掉登录”
这一节是这次扩容最大的教训。
两台 Tomcat 配好轮询之后,问题来了:用户登录后刷新页面,经常又跳回登录页。原因是默认的会话(session)存在单机的内存里——第一次请求落到 web01,session 存在 web01;下一次请求被轮询到 web02,web02 不认识这个 session,就当未登录处理。
会话保持的几种方案,我们对比过:
| 方案 | 原理 | 问题 |
|---|---|---|
| ip_hash | 按客户端 IP 固定后端 | 出口 IP 相同的用户全压一台;节点故障时登录态丢失 |
| session 复制 | Tomcat 集群自动同步 session | 节点超过 4 个性能急剧下降 |
| 统一会话存储 | session 存 Redis/MySQL | 需要改造,但最稳 |
生产上选的是 Redis:多台 Tomcat 共享一份会话数据,任意节点宕机,用户换到另一台照样是登录状态。
接入步骤
1 | # 1. 下载 tomcat-cluster-redis-session-manager(开源项目,GitHub 可搜) |
改完一台,用 rsync 无差异同步到另一台,再重启 Tomcat。升级后的效果:随便停哪台节点,用户无感知,请求被自动转到另一台。
一定记得让两个节点的配置完全一致——session 序列化方式、jar 版本只要有一点不同,就会出现”这台能登录、那台报错”的诡异现象。
四、故障排查实战
集群跑稳之后,值班时遇到的高频故障就是这几类,逐个记一下排查路径。
1. 启动失败:先看三件事
1 | # 1) 服务状态 |
高频原因就三个:端口被占(Address already in use,ss -ntlp | grep 8080 找进程)、JAVA_HOME 没配好、war 包本身有问题。启动成功后的验证动作:ss -lntp | grep 8080 && curl -I http://127.0.0.1:8080。
2. CPU 100%:从进程找到那行代码
1 | top -c # 找到最耗 CPU 的 java 进程 |
栈顶如果是业务代码 → 大概率死循环,找开发修;如果是 GC 线程 → 回到堆内存问题。这套”进程 → 线程 → 栈”的路径,比看监控图猜要快得多。
3. 响应慢 / 线程池满:先看访问日志的耗时
1 | # 访问日志里 %D 字段是耗时(微秒),超过 3 秒的单独看 |
线程数长期贴着 maxThreads、大量线程 BLOCKED,就是线程池打满的信号。往下追通常是慢 SQL 或者第三方接口没设超时——这两个占我们遇到的慢请求原因的一大半。
五、复盘
这次集群改造最值钱的不是配置本身,而是暴露出来的两个认知:
- 有状态的服务不能无脑加节点——session、上传文件、本地缓存,任何”存在本机”的东西,都会在集群下变成故障源;
- 排查顺序固定下来能省时间——启动问题看日志、CPU 问题看线程栈、慢请求看耗时字段,先分类再动手,比上来就重启强。
后来我们给集群加了两条约束:任何新服务上线前确认”状态是否外置”,发版必须逐节点滚动并验证,这两条规矩到现在没出过事故。


