Tomcat 粘性会话导致负载不均,如何无损迁移到 Redis 共享会话
解读
在国内互联网/金融/政企项目里,Tomcat 集群+nginx 是最常见的水平扩展方案。早期为了快速上线,普遍开启「ip_hash」或「jsessionid」粘性会话,导致:
- 热点机器 CPU/内存飙高,其余节点空闲,SLA 不达标;
- 弹性缩容时,被粘住的会话随节点下线而失效,用户频繁重新登录,引发客诉;
- 灰度发布无法按流量比例切流,只能整节点下线,回滚窗口大。
面试时,面试官不仅想知道“怎么改”,更关注:
- 迁移过程是否零中断(无损);
- 数据一致性如何保障;
- 灰度、回滚、监控、性能基线是否闭环;
- 容量评估与压测方案是否专业。
因此,回答必须给出“可落地的 SOP+压测验证+风险控制”,而不是简单贴配置。
知识点
- Tomcat 会话持久化机制:StandardManager、PersistentManager、DeltaManager、BackupManager。
- 会话复制与序列化:java.io.Serializable、JSON、Kryo、Protostuff,及对应包体积、CPU 损耗。
- Redis 高可用模型:哨兵、Cluster、读写分离;Redisson、Lettuce、Jedis 客户端并发模型对比。
- 会话安全:Redis 宕机、网络抖动、序列化兼容性、Session Fixation、Cookie 劫持。
- 无损发布的三大技术:双写(Dual Write)、影子比对(Shadow Compare)、灰度染色(Canary Tag)。
- 性能基线指标:登录 TPS、会话创建 RT、Redis QPS、网络包大小、GC 停顿、CPU sys%。
- 国内合规:等保 2.0 对会话有效期、敏感数据加密存储的要求。
答案
整体分五步:评估→双写→灰度→压测→全量。每一步都给出回滚点,确保“无损”。
第一步:容量与兼容性评估
- 采集现网会话峰值:通过 AccessLog+JMX 拿到“每秒新建会话数”与“平均会话体积”,计算 Redis 所需内存 = 峰值并发 × 平均体积 × 2(高可用)× 1.2(冗余)。
- 序列化选型:若原系统使用 JDK 序列化,体积大、CPU 高,建议改为 Kryo;需保证所有存入 Session 的属性实现 Serializable 或提供 Kryo 注册表。
- Redis 规格:国内云厂商 4C8G 主从版可抗 3 万 QPS,若峰值超 5 万则直接上 Redis Cluster 16 分片,避免后期拆分。
第二步:双写阶段(零中断关键)
- 代码层引入 spring-session-data-redis(或自研 Tomcat Valve),配置 write 策略为“先本地后 Redis”,读取策略为“先 Redis 后本地”,并给关键接口加上 @ConditionalOnProperty,支持开关。
- 部署时保持原有粘性会话不变,用户请求仍落到固定节点;Tomcat 在返回响应前异步将会话写入 Redis,失败不影响本次请求,仅打日志告警。
- 开启“影子比对”Job:每 5 分钟抽样 1% 会话,对比本地与 Redis 的 attribute 哈希值,不一致立即告警并自动回写 Redis,确保数据收敛。
第三步:灰度切流
- 在 Nginx 侧新增 header 标识 x-canary: 1,对应用户 ID 尾号 00-09 的流量取消 ip_hash,采用轮询打到灰度节点组(10% 流量)。
- 灰度节点关闭本地会话持久化,只读 Redis;若 Redis 不可连,则快速失败并降级到“重新登录”,避免阻塞。
- 观察 24 小时:错误率 <0.1%、登录 TPS 持平、Redis 延迟 <5 ms、无影子比对不一致,则进入下一轮 30%、50%……直至 100%。
第四步:性能压测与基线校准
- 在预发环境搭建 1:1 数据规模,使用 Gatling 模拟 8 k 并发,保持 30 min,重点指标:
- 会话创建 RT <20 ms(P99);
- Redis QPS 峰值 12 k,CPU 65% 以下;
- 网络出口流量增加 <15%;
- Young GC 次数增加 <10%。
- 若 RT 超标,则调大 Redisson 的 nettyThreads=32、retryInterval=100 ms,并开启 TCP nodelay;
- 若 Redis 出现热 Key,采用“本地缓存 1 s + 一致性哈希”方式打散,或将会话拆分为 user-base、cart 两个 Key,降低单 Key 带宽。
第五步:全量上线与回滚
- 删除 Nginx ip_hash 配置,统一采用加权轮询;Tomcat 集群全部切换为“只读 Redis”。
- 保留本地会话目录 72 小时,期间若出现重大故障,可在 2 分钟内通过配置中心开关重新启用本地会话,实现快速回滚。
- 上线后持续 7×24 小时监控:Redis 内存增长率、Key 过期命中率、慢查询、BigKey;同时把“会话丢失率”纳入黄金指标,一旦超过 0.01% 立即触发 P1 告警。
通过以上五步,可实现“用户无感知、数据零丢失、回滚秒级”的无损迁移,彻底解决粘性会话带来的负载不均问题。
拓展思考
- 多活架构下,Redis 采用“异地双写+CRDT”还是“主从+半同步”?如何权衡 RPO 与写入性能?
- 若会话体积超过 50 KB(含大型购物车),继续放 Redis 会放大网络瓶颈,是否考虑“本地缓存+Redis 索引”或 Cookie 压缩?对应的压测模型如何设计?
- 等保要求“会话 30 分钟无操作必须失效”,而电商大促期间用户频繁刷新,如何在 Redis 中实现“滑动过期”又不造成热 Key 写放大?
- 当 Redis 单分片故障时,客户端默认重试到其他分片,会话 Key 可能瞬间漂移,如何结合染色 Trace 验证用户请求在多分片间的一致性?
- 未来计划引入 Service Mesh,Sidecar 层也会维护会话状态,如何与 Redis Session 统一模型,避免双重序列化带来的 10% CPU 损耗?