Tomcat 集群搭建与负载均衡
概述
单台 Tomcat 有容量上限,集群是横向扩展的必然选择。集群要解决两个问题:流量分发(负载均衡)与状态一致(会话共享)。本文对比 mod_jk、mod_proxy、Nginx 三种前置方案,重点讲透会话保持与故障转移,并给出从架构选型到落地验证的完整路径。
一、集群整体拓扑
┌──────────────────────────┐
客户端 ───────▶│ 负载均衡器(LB) │
│ Nginx / Apache / 硬件LB │
└─────┬─────────┬─────────┘
│ │
┌────────▼──┐ ┌───▼────────┐
│ Tomcat A │ │ Tomcat B │
│ (8081) │ │ (8082) │
└─────┬─────┘ └─────┬──────┘
│ 会话共享 │
└── Redis / 复制 ──┘LB 负责分发请求,会话存储保证任一节点都能处理带 JSESSIONID 的请求。
二、负载均衡方案对比
| 方案 | 协议 | 特点 | 适用场景 |
|---|---|---|---|
| mod_jk | AJP | 与 Tomcat 原生集成、权重/粘性配置丰富 | Apache 前置的存量架构 |
| mod_proxy | HTTP/HTTPS | Apache 模块,配置简单 | Apache 前置、需灵活转发规则 |
| Nginx 反向代理 | HTTP/HTTPS | 高性能、配置灵活、生态好 | 主流选择 |
| 硬件 LB(F5 等) | 四层/七层 | 极致性能、贵 | 大型互联网站点 |
2.1 mod_jk(AJP)
Tomcat 侧需开启 AJP Connector:
xml
<Connector port="8009" protocol="AJP/1.3"
secretRequired="true" secret="cluster-secret"
redirectPort="8443" />Apache 侧配置 workers.properties:
properties
worker.list=nodeA,nodeB
worker.nodeA.type=ajp13
worker.nodeA.host=10.0.0.11
worker.nodeA.port=8009
worker.nodeA.lbfactor=1
worker.nodeB.type=ajp13
worker.nodeB.host=10.0.0.12
worker.nodeB.port=8009
worker.nodeB.lbfactor=2lbfactor 控制权重(nodeB 承接两倍流量)。mod_jk 支持粘性会话:首次请求把会话绑定到某个节点,后续带同一 JSESSIONID 的请求都转发到该节点。
2.2 mod_proxy(HTTP)
apache
ProxyPass / balancer://tomcatCluster/ stickysession=JSESSIONID
<Proxy balancer://tomcatCluster>
BalancerMember ajp://10.0.0.11:8009 route=nodeA
BalancerMember ajp://10.0.0.12:8009 route=nodeB
ProxySet lbmethod=byrequests
</Proxy>2.3 Nginx 反向代理(推荐)
nginx
upstream tomcat_cluster {
# 权重轮询
server 10.0.0.11:8081 weight=1;
server 10.0.0.12:8082 weight=2;
# 粘性会话:按 JSESSIONID 哈希
ip_hash; # 或 hash $cookie_jsessionid consistent;
}
server {
listen 80;
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;
# 后端健康检查(需 nginx-plus 或脚本)
# proxy_next_upstream error timeout http_502;
}
}Nginx 的优势:配置轻量、性能高、负载均衡算法丰富(轮询/权重/ip_hash/最少连接)、与 K8s Ingress 生态统一。
三、会话保持(Session Affinity)
3.1 三种策略对比
| 策略 | 机制 | 优点 | 缺点 |
|---|---|---|---|
| 粘性会话 | 同一会话固定节点 | 会话不共享也成立 | 节点故障时会话丢失(需兜底) |
| 会话复制 | 节点间同步 | 节点故障不丢会话 | 广播开销、需 Serializable |
| 外部会话存储 | Redis/DB 统一存放 | 无状态化、易扩展 | 引入外部依赖 |
3.2 粘性会话的 JvmRoute
Tomcat 用 jvmRoute 让 LB 能识别会话所属节点:
xml
<Engine name="Catalina" defaultHost="localhost" jvmRoute="nodeA">- 会话 ID 生成后追加
nodeA后缀:JSESSIONID=abc123.nodeA - Nginx 粘性模式据此把请求路由到 nodeA
- nodeA 故障时,请求落到其他节点,
JvmRouteBinderValve检测到会话的 route 失效,自动重新绑定并重写 Cookie
生产建议:粘性会话 + 会话复制/Redis 兜底,兼得性能与可用性。
四、集群会话共享方案
4.1 方案对比
| 方案 | 一致性 | 性能 | 复杂度 | 适用 |
|---|---|---|---|---|
| DeltaManager 复制 | 最终一致 | 中 | 低 | 小集群 |
| BackupManager 备份 | 最终一致 | 中高 | 中 | 中集群 |
| Redis(Spring Session) | 强一致(存储为准) | 高 | 中 | 微服务、弹性扩展 |
4.2 Spring Session + Redis(推荐)
xml
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>java
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
public class SessionConfig {
}配合粘性会话或直接去掉粘性——节点全部无状态,Redis 是唯一会话来源。
4.3 复制方案注意点
- 会话属性必须
Serializable - 集群成员通过
SimpleTcpCluster通信,需要节点间网络互通(通常同网段) - 高并发写会话场景广播量大,慎用大集群全量复制
五、故障转移与健康检查
5.1 健康检查机制
| 层面 | 手段 |
|---|---|
| TCP 探测 | Nginx 检查 8081/8082 端口是否可连 |
| HTTP 探测 | 访问 /health 端点,期望 200 |
| 应用级 | 探活接口检查 DB/Redis 依赖是否正常 |
| Tomcat 自身 | 关闭端口(8005)收到 SHUTDOWN 即退出 |
5.2 Nginx 故障转移策略
nginx
upstream tomcat_cluster {
server 10.0.0.11:8081 max_fails=3 fail_timeout=30s;
server 10.0.0.12:8082 max_fails=3 fail_timeout=30s;
}| 参数 | 含义 |
|---|---|
max_fails | 连续失败多少次判定节点不可用 |
fail_timeout | 判定不可用后冷却时间 |
proxy_next_upstream 控制哪些错误会触发切换到下一节点:
nginx
proxy_next_upstream error timeout http_502 http_503 http_504;5.3 优雅下线流程
1. 通知 LB 摘除节点(Nginx 修改 upstream / 权重置 0)
2. 等待存量请求完成(连接器 drain 模式)
3. 应用探活返回异常,不再接收新流量
4. 节点空闲后停机Tomcat 侧配合:shutdown.sh 会优雅停止——先停止 Connector 接收新连接,再等处理中的请求结束(受 connectionTimeout 与线程池状态约束)。
六、集群部署清单
| 步骤 | 内容 |
|---|---|
| 1. 统一环境 | 各节点 JDK/Tomcat 版本、JAVA_OPTS 一致 |
| 2. 配置隔离 | jvmRoute 各节点不同,端口规划清晰 |
| 3. 会话方案 | 决定粘性/复制/Redis,并验证 |
| 4. 会话 Cookie 配置 | 同一域名下 sessionCookiePath=/,Secure/HttpOnly |
| 5. 日志与监控 | 访问日志集中采集,节点指标接入监控 |
| 6. 验证场景 | 登录取证(登录→刷新→切节点→仍在线) |
| 7. 压测 | 全集群压测,观察分发是否均衡 |
七、常见问题排查
| 现象 | 原因 | 处理 |
|---|---|---|
| 登录后刷新掉线 | 会话未共享,粘性失效 | 检查 Redis 会话方案或粘性配置 |
| 流量不均衡 | 权重设置错误 / 某节点健康检查失败 | 核对 lbfactor/weight 与探活 |
| 502/504 频繁 | 节点处理能力不足或超时设置过短 | 检查 maxThreads、proxy_read_timeout |
| 节点故障后新请求仍指向它 | LB 未及时摘除 | 缩短健康检查间隔,配置 max_fails |
| JSESSIONID 后缀未生效 | jvmRoute 未配置 | Engine 加 jvmRoute 并重启 |
参考链接: