news 2026/9/2 13:51:56

Tomcat集群Session共享:Redis集中存储方案全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Tomcat集群Session共享:Redis集中存储方案全解析

简介:面向Java Web开发者,这是一套基于Redis的Tomcat会话管理工具包,旨在解决分布式环境下多节点Session不共享的痛点。资源整合tomcat-redis-session-manager,针对Tomcat 7、8.0、8.5、9及JDK 1.7/1.8提供不同适配版本,覆盖常见运行环境。压缩包共40个文件,其中20个Java源码用于二次开发,15个Jar依赖包含Jedis、commons-pool2及session管理器核心库,5个XML配置示例则给出context.xml等典型配置,整体大小仅1.99MB。已有610人学习/下载。目录按Tomcat版本分别组织,方便按需选用;资料内提供的源码和配置模板,可帮助理解SessionStore接口如何与Redis交互,掌握会话持久化与失效策略,适合需要集成或扩展该组件的开发者。

1. 这项目到底解决了什么问题

先讲个我最近遇到的案例。有个朋友做电商系统,单体应用在单台服务器上跑得好好的,后来为了扛大促流量,上了Nginx做负载均衡,后端挂了三台Tomcat。上线第二天就开始有人反馈:登录状态忽好忽坏,一会儿还显示用户名,刷新一下又跳回登录页了,购物车也时不时清空。

我问他Session怎么处理的,他说没处理,就直接三台服务器部署了同一个war包。这就是典型的Session不一致问题:用户在Tomcat A上登录,Session只存在A的内存里,下一次请求被Nginx转发到了Tomcat B,B的内存里没有这个Session,自然就认为用户未登录。

1.1 Session到底存在哪

在讨论解决方案之前,先把Session这个概念拆清楚。很多刚入行的朋友会把Cookie和Session搞混,实际上两者是配合关系:Session数据保存在服务端,Cookie里只放了一个Session ID,也就是我们常说的JSESSIONID。浏览器每次请求都会带着这个ID,服务端根据ID找到对应的Session对象。

单机时代这没什么问题,内存里找东西是很快的。一旦到了集群环境,麻烦就来了:Session是跟着服务器走的,不跟着用户走。用户的请求被负载均衡切到哪台机器,决定了它能访问到哪份Session数据。

1.2 为什么不能靠Nginx的IP_HASH硬扛

肯定有人会说:让Nginx做IP_HASH,同一个IP固定打到同一台服务器不就行了?这个方案在不少小项目里确实能用,但有几个隐患。

IP_HASH依赖客户端IP做哈希,如果用户是移动网络,IP会频繁变化,哈希结果跟着变,Session照样丢。公司出口是统一NAT网关的话,所有员工IP都一样,那等于没做负载均衡,全打到一台机器上。而且一旦某台Tomcat宕机,它承载的那部分用户Session全部丢失,一个都跑不掉。

还有一个深层问题:IP_HASH只是把Session分布在不同节点上,并没有真正实现Session共享,它治标不治本,让问题从"失效"变成了"概率性失效"。

1.3 为什么要用Redis做集中存储

把Session从各节点的内存里挪出来,放到一个所有节点都能访问的公共组件里,这就是集中式Session管理的核心思路。可选载体无外乎数据库、Memcached、Redis这几种。

数据库方案的问题很明显:Session是高频读写数据,每次请求都要读写数据库,IO压力太大。Memcached虽然快,但纯内存存储,重启就丢。Redis天然适合干这个活:读写速度快、支持持久化、自带过期机制,一揽子解决了性能、可靠性和过期清理的问题。

tomcat-redis-session-manager这个开源项目,就是干这件事的:它把Tomcat默认的内存Session存储替换成Redis存储,应用代码一行不用改,只改配置就能实现集群下的Session共享。

2. 核心工作原理拆解

2.1 Manager和Valve是如何配合的

Tomcat内部的Session管理逻辑主要集中在Manager组件上。官方默认的StandardManager把Session存放在JVM堆内存里,而tomcat-redis-session-manager做的事情很直接:换掉Manager的实现,让Session的创建、读取、保存、销毁全部走Redis。

这个项目里有两个核心类,一个是RedisSessionManager,另一个是RedisSessionHandlerValve。很多人在配置里看到这两个类名,但不知道它们各自扮演什么角色。

RedisSessionManager负责底层数据操作:比如新Session要生成什么ID、Session的过期时间怎么设置、怎么从Redis里把一个序列化后的Session读回来。RedisSessionHandlerValve则像一个"收尾的监工":每个HTTP请求处理完毕之后,它会主动触发一次Session保存,把请求过程中发生的Session变更写回Redis。

为什么需要Valve来做保存动作?因为Servlet规范里,Session什么时候更新、什么时候保存并没有强制约定。如果依赖业务代码里手动调用setAttribute之后的某次保存,很难控制时机。Valve机制能确保:一个请求处理完,不管业务代码写了什么,Session状态一定同步到了Redis。

2.2 一次请求的完整数据链路

把整个过程走一遍会清楚很多。浏览器第一次访问应用时,Tomcat端还没有对应的Session,RedisSessionManager会创建一个新的Session对象,生成一个唯一的JSESSIONID,然后把Session信息写入Redis,同时通过响应头把JSESSIONID下发到浏览器。

浏览器后续请求带到这个JSESSIONID后,RedisSessionManager会根据这个ID去Redis里查询,把对应的Session数据读取出来,反序列化成一个Session对象,挂到当前请求上。业务代码正常使用,不感知Session到底存哪里。请求结束,Valve触发保存,Session对象序列化后写回Redis。

整个过程对应用是透明的,这也是这个方案最大的价值:改造成本极低,老项目也能快速接入。

2.3 Redis里的数据长什么样

我习惯用Redis Desktop Manager或者redis-cli直接查看,实际存储结构很直观。每个Session在Redis里是一个Hash类型的数据结构,键就是Session ID,字段包括creationTime、lastAccessedTime、maxInactiveInterval这些元数据,以及业务实际存入Session的各个属性。

这里有个细节值得注意:整个Session对象序列化之后,会作为一个整体字段存进去,还是拆开存?取决于具体实现。大多数情况下是整体序列化到某个字段,加载时再整体反序列化回来。这意味着Session里存的对象必须可序列化,这个点后面专门讲,它是坑最多的地方。

2.4 过期清理机制

Session的过期时间是核心配置项。这个项目会把maxInactiveInterval映射成Redis键的TTL,Redis到了时间自动删掉这个键,Session就自然"过期"了,不需要Tomcat这边跑定时任务去扫描清理,这是用Redis很划算的一点。

但有一个副产品要注意:Redis删除键是惰性的,如果某个Session长时间没有请求访问,键会一直存在直到TTL到期被清理;如果应用里依赖"Session发生某操作时重置过期时间",要确认Manager的实现是否会在每次访问时自动更新TTL,常见的做法是请求进来时重新设置过期时间,所以配置时要保证这个逻辑是对的。

3. 部署实操:从零到能跑

3.1 版本选择与环境准备

这个项目最早由James Coleman开发并开源,后来社区涌现了多个维护分支,比如支持Tomcat 8的branbanan版、支持Tomcat 9的jplock版等。不同分支的包名和类名会有差异,这是新手最容易懵的地方。

我的建议是先确认自己的Tomcat版本再选对应分支。如果你用的是Tomcat 8.5或9.x,需要找到对应的分支源码自行编译,官方release包不一定覆盖你当前的版本。编译环境需要JDK和Maven,先确认这两个基础组件已装好。

Redis这边没有特别苛刻的要求,3.x以上版本都能正常工作。需要注意Redis最好是单独部署或者集群部署,不要和Tomcat挤在一台机器上,否则整个架构的可用性没得到本质提升。

3.2 从源码构建与jar包依赖

代码获取后,进入项目根目录执行Maven构建命令:

mvn package -DskipTests

跳过测试是因为这个项目比较老,部分测试用例针对的是旧版Tomcat API,在当前环境跑很容易报错,但不影响核心代码编译。构建成功后,target目录下会生成一个jar包,这就是核心组件。

除了这个jar本身,还需要将依赖的Jedis和Commons Pool 2复制到Tomcat的lib目录下。Jedis是Redis的Java客户端,Commons Pool 2是连接池实现。如果漏掉这些依赖,Tomcat启动时会直接报NoClassDefFoundError,提示找不到某个类。

所有jar包就位之后,修改配置文件即可,不需要动任何业务代码。

3.3 context.xml完整配置示例

以Tomcat 8.5环境为例,在conf/context.xml的Context节点下添加Manager和Valve定义:

<Context> <Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" /> <Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager" host="127.0.0.1" port="6379" database="0" password="yourpassword" timeout="2000" maxActive="100" maxIdle="20" minIdle="5" maxWait="3000" maxInactiveInterval="3600" serializationStrategy="JAVA" /> </Context>

这里逐项解释一下配置的含义。host和port是Redis的连接地址,本机测试填127.0.0.1:6379。database是Redis的db编号,默认0。如果Redis设置了密码,password必须填对,否则启动后在第一次Session操作时会报认证失败。

timeout是连接Redis的超时时间,单位毫秒,建议2000左右,太短在Redis压力大时容易误判超时,太长会拖慢请求。maxActive、maxIdle、minIdle、maxWait这几个是Jedis连接池的参数,分别对应最大活跃连接数、最大空闲连接数、最小空闲连接数和获取连接的最大等待时间。maxInactiveInterval是Session过期时间,单位秒,这个值同时决定了Redis里键的TTL。

如果项目使用的分支类名不同,比如org.apache.catalina.session.RedisSessionManager,把配置里的className替换成对应全限定名即可。

3.4 修改应用内web.xml和验证步骤

为了让Session过期行为更可控,建议在应用的web.xml里也配置Session超时时间:

<session-config> <session-timeout>60</session-timeout> <cookie-config> <http-only>true</http-only> </cookie-config> </session-config>

配置完成后,分别在三台Tomcat上重复同样的操作。然后启动所有节点,进行一轮完整验证。

验证分三步走。第一步,访问应用,登录后打开浏览器开发者工具,找到Cookie里JSESSIONID的值并记下来。第二步,用redis-cli执行keys *,能看到以Session ID为键名的Hash记录,说明Session确实写入Redis了。第三步,也是最关键的一步,把当前登录用户的这台Tomcat直接杀掉,刷新页面让请求打到另一台节点,如果用户仍然处于登录状态,说明Session共享生效了。

这一步验证是最有成就感的,因为它直观地证明了Session没有丢在宕机的那台机器上。

4. 序列化机制:这个项目的命门

配置搞定了,集群环境下登录态也稳定了,但我在实际使用中发现,真正让开发者头疼的往往是序列化相关的异常。Session要写进Redis,就意味着Session里存的所有对象都必须能序列化,这一点在最开始没做好架构规划,后面就是无底洞。

4.1 为什么序列化是核心问题

Java对象的序列化,简单理解就是把内存里的对象变成字节流,以便传输或存储。Redis存储的是字节,所以Session对象必须经历序列化和反序列化的过程。

默认的serializationStrategy是JAVA,也就是JDK原生的ObjectOutputStream和ObjectInputStream。这个方案的好处是零额外依赖,开箱即用。坏处是序列化后的内容臃肿、可读性差,并且强制要求所有属性对象实现java.io.Serializable接口。

我在一个项目里就遇到过这种情况:业务方图省事,把分页查询的结果对象、Excel导出工具类对象、甚至某些第三方SDK里的内部对象直接塞进了Session,完全没有考虑可序列化要求。当时改起来特别痛苦,因为这些第三方类内部再引用了其他不可序列化的类,牵一发动全身。

4.2 常见的序列化异常和处理思路

NotSerializableException是最常见的报错,信息里会明确指出哪个类没有实现Serializable。解决办法分三种:第一种是让这个类实现Serializable,补上serialVersionUID;第二种是标记这个字段为transient,告诉序列化机制跳过它;第三种是如果是第三方类改不了,就在存入Session前把数据转换成可序列化的DTO结构。

还有一类问题是InvalidClassException,一般发生在代码更新后没有维护serialVersionUID,类结构变了但序列化ID没变,反序列化时新旧版本对不上。批量Session失效、用户集体掉线往往就是这个原因。所以在设计Session里存储的对象时,一定要显式声明serialVersionUID,这个习惯能省很多线上的麻烦。

另外还有一种可能性很多人没意识到:如果多个应用共享同一个Redis的Session(比如单点登录场景),所有应用必须持有完全一致的类定义,包括包名、类名、字段结构。否则一个应用存进去一个对象,另一个应用反序列化时找不到类,直接抛ClassNotFoundException。

4.3 是否要切换到其它序列化方案

部分fork版本提供了FASTJSON、KRYO等序列化策略,切换后能解决部分不可序列化的问题。但我不建议一上来就换,原因有两个。第一,非默认序列化方案往往需要在类上增加额外注解,改造成本并不比修序列化接口低。第二,这些方案对复杂对象图的支持不一定完善,反而可能出现一些只在特定类型上触发的诡异问题。

我的建议是:保持JAVA序列化,把Session里存的东西管好。能用基本类型、String、简单DTO解决的,绝不放复杂对象。Session本来就不应该存储重量级数据。

5. 常见问题与排查技巧实录

配置完成后,真正在集群环境里跑起来,问题才会陆续浮出来。我把自己和其他开发者实际遇到的高频问题整理成了一张速查表,方便你遇到问题时直接定位。

现象大概率原因排查思路
启动后首次访问报JedisConnectionExceptionRedis地址端口不通或密码错误redis-cli手动连接验证,检查防火墙和认证
用户登录后一会儿就掉线maxInactiveInterval设置太短确认Redis的TTL是否对应,调整配置
某个用户操作时报NotSerializableExceptionSession中存放了不可序列化对象按报错信息定位类,改造为可序列化
集群切换后报ClassNotFoundException各应用节点类定义不一致检查jar包版本、包名全限定名
请求变慢,Redis CPU偏高Session存了较大数据或连接池配置过小优化Session内容,调大maxActive
多个请求同时改Session互相覆盖没有引入Valve配置或配置错误检查Valve是否在Manager之前加载

5.1 一个典型的掉线问题排查过程

有一次接到反馈,某个客户环境用户频繁掉线,但测试环境一切正常。我远程看了日志,发现报错的是Redis连接超时。进一步排查发现,客户的Redis和Tomcat不在同一个网段,中间隔着防火墙,每次请求都要跨机房访问,网络延迟高达几十毫秒,再加上并发量一上来,连接池被占满,后续的Session操作全部阻塞超时。

当时的处理是两件事:第一,把Redis实例做了一次就近迁移,和Tomcat放到同机房内网;第二,把Jedis连接池的maxActive调大,并合理设置timeout。调整之后问题消失。这个案例说明,Session存储从本地内存变成远程Redis,本质上是引入了远程IO,网络质量直接决定整个系统的稳定性和响应速度。

5.2 注意Redis持久化和高可用

很多人觉得Redis是缓存,挂了就挂了,重启就行。在Session场景下,这个想法很危险。如果Redis没有开启持久化,一旦机器重启,所有Session数据全部清空,后果是全体用户强制下线。

生产环境建议开启AOF持久化,设置合理的刷盘策略。如果对可用性要求更高,直接上Redis主从加哨兵或Redis Cluster,保证Redis自身的高可用。Session是业务状态的载体,它的存储层必须和数据库存储层一个级别的重视程度。

5.3 和Spring Session怎么选

如果你的项目已经引入了Spring框架,或者本身就是Spring Boot项目,我可能会建议你直接考虑Spring Session。它和Spring生态集成更顺畅,支持Redis、JDBC等多种存储后端,而且持续在维护,社区活跃度远高于tomcat-redis-session-manager。

反过来,如果是老旧的Spring MVC项目,或者压根就是纯Servlet项目,不想为了Session功能升级整个技术栈,那tomcat-redis-session-manager这种配置级的方案很合适。它在Web容器层面解决问题,应用代码不用动,接入成本最低。

我个人在实际操作中的体会是:任何技术方案都要结合自己项目的现状和长期演进方向来判断。tomcat-redis-session-manager解决了我手头那个老项目的燃眉之急,代价极低,收益立竿见影;但如果让我从零搭建一个新系统,我会直接选Spring Session。另外再分享一个细节经验:接入之前,先花半天时间梳理一下项目里所有往Session里塞的数据,做一个"可序列化体检",把不该放Session的东西清出去。这个动作能让你在后续使用中少踩90%的坑。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 13:51:28

计算机单片机毕设实战-基于 STM32 的车载酒驾防护与定位短信告警装置设计 基于 STM32 单片机的酒精检测阈值可调控制系统设计与实现(010206)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机&#xff0c;Java、小程序技术领域和毕业项目实战 ✌️…

作者头像 李华
网站建设 2026/9/2 13:51:27

开源工具Umbrella:从信息聚合到自动化内容交付的工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/2 13:50:44

3步把微信聊天记录导出成Word、CSV与HTML,本地生成年度聊天报告

3步把微信聊天记录导出成Word、CSV与HTML&#xff0c;本地生成年度聊天报告 【免费下载链接】WeChatMsg 提取微信聊天记录&#xff0c;将其导出成HTML、Word、CSV文档永久保存&#xff0c;对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending…

作者头像 李华
网站建设 2026/9/2 13:49:57

基于Python与MediaPipe实现引体向上自动计数:从姿态估计到状态机逻辑

简介&#xff1a;本资源是一套基于Python的引体向上动作自动计数算法实现方案&#xff0c;面向计算机科学、电子工程及应用数学等相关专业本科生与研究生&#xff0c;解决健身动作识别与运动次数量化中的姿态检测与轨迹分析问题&#xff0c;适用于课程实践、学期项目或毕业设计…

作者头像 李华
网站建设 2026/9/2 13:49:29

喷码缺陷检测实战:轻量CNN+传统图像处理的产线级方案

简介&#xff1a;本资源是一套面向计算机、自动化及人工智能方向本科生的毕业设计级项目&#xff0c;聚焦工业质检场景中的喷码缺陷智能识别问题&#xff0c;适用于课程设计、期末大作业及毕业设计实践。项目基于Python实现&#xff0c;融合图像预处理、OCR字符提取与规则比对、…

作者头像 李华