1. 项目背景与核心需求
在现代Web应用架构中,Session共享是分布式系统必须解决的关键问题。当你的应用从单机部署扩展到多台Tomcat实例时,用户第一次访问可能落在服务器A,第二次请求却被Nginx负载均衡到服务器B,此时传统的Tomcat内存Session机制会导致用户频繁退出登录。
我曾为一个电商项目处理过这个痛点:大促期间新增了3台Tomcat服务器,但用户购物车数据频繁丢失。通过引入Redis+Redisson方案,最终实现了:
- 会话数据在多台Tomcat间实时同步
- 零代码侵入式改造(基于Servlet Filter)
- 支持单机/集群两种Redis部署模式
- 会话过期时间精准控制
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与组件选型
2.1 版本兼容性矩阵
| 组件 | 推荐版本 | 关键依赖 |
|---|---|---|
| Nginx | 1.20+ | --with-http_stub_status_module |
| Tomcat | 9.0.x | Servlet 4.0+ |
| Redis | 6.2+ | 启用ACL |
| Redisson | 3.17+ | JDK 11+ |
注意:Redis 5.x以下版本存在集群模式下会话丢失的已知问题,建议使用6.2+版本
2.2 系统资源规划
-
单节点模式(开发环境):
- 2核CPU / 4GB内存
- Redis最大内存限制1GB
- Tomcat堆内存-Xmx1024m
-
三节点集群(生产环境):
- 每个节点4核CPU / 8GB内存
- Redis内存限制4GB
- 需要配置persistence(AOF+RDB)
3. Nginx配置优化
3.1 负载均衡策略
nginx复制upstream tomcat_cluster {
# 加权轮询(带会话保持)
ip_hash;
server 192.168.1.101:8080 weight=3;
server 192.168.1.102:8080 weight=2;
server 192.168.1.103:8080 weight=1;
# 故障转移检测
check interval=3000 rise=2 fall=3 timeout=2000 type=http;
check_http_send "HEAD /health HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}
关键参数说明:
ip_hash:基于客户端IP的会话保持(非必须,但可减少Session同步频率)check:主动健康检查(需安装nginx_upstream_check_module)
3.2 反向代理配置
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_connect_timeout 75s;
proxy_read_timeout 1800s;
}
4. Tomcat与Redis集成
4.1 Redisson配置(单节点)
xml复制<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.17.7</version>
</dependency>
java复制Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setPassword("your_password")
.setDatabase(0)
.setTimeout(3000);
RedissonClient redisson = Redisson.create(config);
4.2 集群模式配置
java复制Config config = new Config();
config.useClusterServers()
.addNodeAddress(
"redis://192.168.1.201:6379",
"redis://192.168.1.202:6379",
"redis://192.168.1.203:6379")
.setPassword("cluster_password")
.setScanInterval(5000);
实测建议:生产环境建议使用DNS SRV记录而非硬编码IP
5. Session共享实现
5.1 过滤器配置
java复制public class RedisSessionFilter implements Filter {
private RedissonClient redisson;
@Override
public void init(FilterConfig config) {
// 初始化Redisson客户端
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpSession session = request.getSession();
// 会话同步逻辑
RMap<String, Object> sessionMap = redisson.getMap("session:" + session.getId());
sessionMap.expire(30, TimeUnit.MINUTES);
chain.doFilter(req, res);
}
}
5.2 性能优化技巧
-
序列化选择:
- 优先使用FST或Kryo(相比JDK序列化提升3-5倍性能)
java复制config.setCodec(new FstCodec()); -
批量操作:
java复制RBatch batch = redisson.createBatch(); RMapAsync<String, Object> map = batch.getMap("session:" + sessionId); map.putAsync("key1", value1); map.putAsync("key2", value2); batch.execute(); -
本地缓存:
java复制config.useClusterServers() .setLoadBalancer(new RoundRobinLoadBalancer()) .addNodeAddress(redisNodes) .setSubscriptionMode(SubscriptionMode.MASTER) .setSubscriptionConnectionMinimumIdleSize(1) .setSubscriptionConnectionPoolSize(50) .setSlaveConnectionMinimumIdleSize(24) .setSlaveConnectionPoolSize(64) .setMasterConnectionMinimumIdleSize(24) .setMasterConnectionPoolSize(64);
6. 生产环境验证
6.1 压力测试指标
| 场景 | TPS | 平均响应时间 | 错误率 |
|---|---|---|---|
| 单Tomcat+本地Session | 1250 | 38ms | 0% |
| 集群+Redis单节点 | 980 | 52ms | 0.2% |
| 集群+Redis集群 | 1150 | 41ms | 0% |
测试工具:JMeter 5.4.1,500并发持续10分钟
6.2 常见故障排查
-
会话丢失问题:
- 检查Redis maxmemory-policy配置(建议volatile-lru)
- 确认Redisson的watchdog机制是否启用
java复制config.setLockWatchdogTimeout(30000); -
性能瓶颈:
- 使用Redis-cli --latency检测网络延迟
- 调整Redisson线程池参数
java复制config.setThreads(16).setNettyThreads(32); -
集群脑裂处理:
bash复制# 查看集群状态 redis-cli --cluster check 192.168.1.201:6379
7. 安全加固建议
-
Redis ACL:
bash复制
ACL SETUSER session_user on >s3cr3t_pwd ~session:* +@all -
传输加密:
java复制config.useSingleServer() .setAddress("rediss://127.0.0.1:6379") // 注意rediss协议 .setSslEnableEndpointIdentification(true); -
会话固定攻击防护:
java复制if(request.getSession(false) != null) { request.changeSessionId(); }
我在实际部署中发现,当Redis集群发生主从切换时,Redisson的RBatch操作可能出现部分成功。这时需要添加重试机制:
java复制int maxRetries = 3;
while (maxRetries-- > 0) {
try {
batch.execute();
break;
} catch (RedisException e) {
Thread.sleep(1000);
}
}
