Tomcat 8 Session共享实战:用Redis解决分布式会话一致性

发布时间:2026/10/14 22:15:09
Tomcat 8 Session共享实战:用Redis解决分布式会话一致性 简介面向 Tomcat 8 与 Redis 集成的 Java Web 开发者这份资源提供了 tomcat-redis-session-manager 所需的完整 jar 包集合用于将 HTTP Session 从 Tomcat 内存迁移到 Redis解决分布式环境下会话共享与高并发性能问题。压缩包为 zip 格式共 6 个文件主要包含 4 个 jar 依赖、1 个 context.xml 配置示例和 1 个 txt 说明文档整体约 445KB可直接放入 Tomcat 的 lib 目录并按示例修改配置。资源覆盖了 RedisStore 核心管理器、Jedis 客户端以及 commons-pool2 连接池等依赖同时附带序列化配置说明帮助开发者快速完成 Tomcat8 环境下的 session 共享配置与优化。目前已有 940 人学习下载适合需要在 JDK 1.8 Tomcat 8 环境中实现 session 共享的中高级后端开发人员。1. 为什么要在 Tomcat 8 里给 Session 找一个 Redis 归宿从单机内存到分布式共享当你的 Tomcat 8 应用开始出现用户刚登录就掉线的毛病时十有八九不是业务代码的问题而是 Session 没地方放。默认情况下 Tomcat 把 Session 存在自己的 JVM 内存里单机没问题一旦前面挂了负载均衡请求被分发到另一台机器用户就要重新登录。tomcat-redis-session-manager 这套包集合解决的问题很直接把 Session 从 JVM 内存挪到 Redis让集群里的每一台 Tomcat 都共享同一份会话数据。这篇笔记会从包集合的组成讲起带你把 jar 下载、组装、配置、验证走一遍并把我在接入过程中踩过的坑逐个说清楚。适合已经会部署 Tomcat 8、正准备为集群会话共享做方案的开发者。2. tomcat-redis-session-manager 是什么它把 Session 从内存搬到了 Redis2.1 这个包集合到底由哪些 jar 组成第一次接触包集合下载这个说法时很容易把它理解成下载一个大包里面什么都有。实际上这套东西是一组依赖而不是孤立存在的单个 jar。最核心的是本体 jar它实现 Tomcat 的 Manager 接口负责接管 Session 的创建、读取、保存和销毁流程第二个是 Redis 客户端也就是 jedis负责和 Redis 服务端建立连接并读写数据第三个是连接池实现 commons-pool2它让 jedis 能够复用连接而不是每个请求都重新握手一次。这三个文件缺一个Tomcat 启动后要么直接报 ClassNotFound要么跑到一半才报连接池异常。有些下载站会把这三个文件打成一个完整包发布有些人则只发布本体 jar依赖需要你自己按 pom 文件补齐。怎么判断你手上拿到的是不是完整包直接用 JDK 自带的 jar 命令看本体里有没有 jedis 的类路径jar tf tomcat-redis-session-manager-版本.jar | grep redis/clients/jedis/Jedis.class如果命令有输出说明这个本体已经打了 fat jar把 jedis 的类也包进来了你不需要再单独放 jedis如果没有输出就老老实实按接下来的依赖清单把 jedis 和 commons-pool2 补齐。这个检查我每次下载完都会做胜过启动时报错再回来猜。参数说明jar tf 里的 tf 是 list table of contents 的意思输出 jar 包内所有文件的完整路径grep 过滤出 jedis 的类路径。如果不放心可以把 grep 关键词换成 commons/pool2一样能判断 commons-pool2 是否被打进去。切记只看文件存在不够还要注意版本后面会展开讲。2.2 和内置 Session 相比它解决了什么问题Tomcat 默认的 StandardManager 把 Session 存放在当前 JVM 的堆内存里。单机部署时一切正常一旦做了多节点集群问题就立刻浮现Nginx 把第一个请求分给 A 节点登录态写进了 A 的内存第二次请求被分到 B 节点B 的内存里没有这个 Session于是 Tomcat 创建一个新的空 Session用户眼睁睁看着自己刚登录的账号变成未登录。tomcat-redis-session-manager 的核心思路很朴素把 Session 放到一台所有 Tomcat 都能访问的 Redis 上节点只负责读和写不负责独占数据。这样任何一个节点收到请求都能从 Redis 取回同一份会话。对比项StandardManager 内存 SessionRedis Session Manager存储介质单机 JVM 堆内存独立 Redis 服务端节点故障会话随进程丢失会话还在 Redis其他节点可接管应用重启会话丢失会话保留可从其他节点恢复跨节点共享不支持天然支持额外依赖无jedis、commons-pool2单请求额外开销无一次内网网络往返约 0.52ms可观测性极差可用 redis-cli 随时查看在线会话数表格里的对比看着简单放到生产环境里却会引出几个容易被忽略的边界。比如 Session 里存放的对象必须实现 java.io.Serializable否则写入 Redis 时会直接抛 NotSerializableException。很多团队在选型时关注的是能不能共享却没意识到这个约束会反过来要求业务代码里的实体类全部实现序列化接口。再比如 Redis 一旦故障所有节点的会话读写都会报错而不是平滑降级到内存这一点必须在架构设计时就做好心理预期。2.3 版本与 Tomcat 8 的匹配逻辑标题写的是 Tomcat 8但 Tomcat 8 本身有 8.0 和 8.5 两个大的小版本线两者底层 API 有差异。社区维护的 tomcat-redis-session-manager 通常针对某个具体小版本编译下载时如果只看到for tomcat8这种笼统标注最好再确认一下它是否声明了 8.0.x 还是 8.5.x。我一般会用一条命令直接看本体 jar 的清单信息比下载页的描述可靠得多unzip -p tomcat-redis-session-manager-版本.jar META-INF/MANIFEST.MF这个命令把 jar 包里的 MANIFEST.MF 输出到终端。文件里通常会记录编译用的 JDK 版本、打包时间有的维护者会写入依赖的 Tomcat 版本号。拿到这些信息后再和你本机运行的 Tomcat 版本对照。同一个 jar 在 8.0 上运行正常换到 8.5 启动时报 AbstractMethodError一般就是接口默认方法的问题。另外一个匹配点出在 jedis 身上。老版本的分支通常绑定 jedis 2.x新分支用 jedis 3.x而 jedis 3.x 对 commons-pool2 的版本有明确要求。下载时注意看本体 jar 的 pom 依赖描述里面写了 jedis 什么版本就尽量用声明版本不要自己拍脑袋升到最新。曾有人为了用新不用旧把 jedis 升到 3.4结果和旧分支写的序列化逻辑不兼容Session 能写进去但读出来是乱码这种问题定位非常费时。版本匹配这件事说到底是以 jar 包编译时的依赖为准而不是以自己机器上现有的版本为准。这一条也解释了为什么标题里的包集合下载这么重要你拿到的应该是一组互相兼容的 jar而不是零散拼凑的文件。3. 下载和组装 tomcat-redis-session-manager 的包集合三种可靠路径3.1 路径一通过 Maven 拉取依赖并导出 jar如果你的工程本身是 Maven 工程这是最省心的路径。先在自己的 pom.xml 里临时引入目标依赖引入之后不需要真正编译进项目只需让 Maven 解析出完整的依赖树。然后使用 maven-dependency-plugin 把运行期依赖拷贝到一个临时目录mvn dependency:copy-dependencies -DoutputDirectory./lib -DincludeScopecompile逻辑说明copy-dependencies 会解析当前工程的所有依赖并把它们复制到指定目录。这里的关键是 -DincludeScopecompile它只拷贝编译期就需要拿到的 jar测试用的 junit 之类的不会被带出来避免污染 Tomcat 的 lib。执行后lib 目录下会看到本体 jar、jedis、commons-pool2 以及一些传递依赖比如 commons-logging。你要做的不是把它们全拖进 Tomcat而是挑出 Tomcat 自带的 lib 里没有的文件。参数说明-DoutputDirectory 指定输出目录按需改名如果项目里同时存在多个版本的 jedis执行前建议先看一眼依赖树确认 Maven 冲突调解选中的最终版本mvn dependency:tree -Dincludesredis.clients:jedis这条命令只输出 jedis 相关的依赖路径。看到版本后再决定要不要在 pom 里显式声明版本号做覆盖。特别注意如果工程里已经有 spring-data-redis 之类的东西它很可能带了一个相同 groupId 但不同版本的 jedis这时两个版本会被 Maven 调解成一个导出结果就未必是 session-manager 期望的版本。我一般会在看到树结构后直接在本体依赖旁边再声明一次 jedis 版本强制覆盖。3.2 路径二直接下载预编译好的 jar 包不走 Maven 的团队最常见做法是去 Maven 中央仓库或项目 Release 页面搜索 tomcat-redis-session-manager 的构件手动下载。这里有个容易绕弯的细节Release 页面经常同时提供源码压缩包和 jar 附件下载时不要拿错了。源码包是让你编译用的不是运行时包集合把源码解压后丢进 Tomcat lib 是最常见的新手操作结果当然启动不起来。下载时我习惯先把所有需要的 jar 放到一个临时目录再从 Maven 仓库把对应构件的 pom 文件也下载下来用文本编辑器打开看它的 节。pom 里声明了哪些依赖你就下载哪些不多不少。命令可以这样写curl -O https://你的仓库地址/.../tomcat-redis-session-manager.jar curl -O https://你的仓库地址/.../jedis.jar curl -O https://你的仓库地址/.../commons-pool2.jar这里的地址我故意用尖括号包裹因为不同版本的构件路径会变化直接给死链没有意义。关键是理解三个 jar 必须来自同一套依赖声明而不是分别从不同版本号里拼凑。下载完成后回到第 2.1 节教的 jar tf 命令验证本体是否包含了依赖类。如果本体已经是 fat jar那么 pom 里的依赖可能就是 provided 或 optional你就不要再额外放 jedis否则两个地方都有相同类类加载顺序一旦改变会出现稀奇古怪的 NoSuchMethodError。参数说明curl 命令加 -O 表示保存为远程文件名如果你是 Windows 服务器用浏览器下载也行但下载后务必检查文件名是否被浏览器改名成 .txt这种小问题也值得留意。检查完文件名之后还要看一眼压缩包完整性能不能解压很多内网下载站会断点导致 jar 截断放到 Tomcat 里会报 ZipException定位很长时间都想不到是下载不完整。3.3 路径三自己编译源码得到 jar适合定制如果你只是想下载使用我不推荐自己编译纯粹增加时间成本。但如果你需要定制 Session 在 Redis 里的 key 前缀、切换序列化方式、或者修改 Session 的并发写策略那就必须走源码编译这条路。做法是从版本仓库把对应分支拉下来用 Maven 打包git clone 源码仓库地址 tomcat-redis-session-manager-src cd tomcat-redis-session-manager-src mvn -DskipTests package逻辑说明clone 之后进入工程目录执行 mvn package 会让 Maven 完成编译、测试、打包。这里我习惯加 -DskipTests 跳过测试因为不少测试用例会启动一个内置 Redis 或断言具体行为没有测试环境时只会白白失败当然如果你本机正好有 Redis 服务也可以去掉这个参数跑一遍完整测试能得到更明确的版本兼容性反馈。打包完成后本体 jar 在 target 目录下依赖 jar 在你的本地 Maven 仓库里。参数说明-DskipTests 只会跳过程序员写的 JUnit 测试代码但不会跳过 test-compile 编译阶段如果想完全跳过测试编译可以用 -Dmaven.test.skiptrue。这两者的区别在调试依赖问题时偶尔会成为关键点。编译之前还要确认本机 JDK 版本Tomcat 8 一般要求 JDK 7 以上建议直接用 JDK 8避免用新版本 JDK 编译出的字节码和 Tomcat 8 的类加载器不兼容。自己编译往往会有个项目打包方式的坑有的分支默认用 maven-shade-plugin 打 fat jar把 jedis 和 commons-pool2 全部揉进一个文件里。fat jar 部署时确实省事但如果你应用的 WEB-INF/lib 里恰好也有 jedis两个类加载器都能加载同名类实际调用哪个版本取决于 Tomcat 的类加载顺序Java 的委托模型在 Tomcat 里也会因为 WebappClassLoader 而有不少易变处。所以我自己编译时一般把 shade 插件关掉保留 classic jar再手动带依赖。3.4 把 jar 正确放进 Tomcat 的 lib 目录无论哪条路径得来的 jar最终都要复制到同一个位置$TOMCAT_HOME/lib。注意是 Tomcat 的全局 lib不是你的应用 WEB-INF/lib。这两者区别很重要session-manager 是 Tomcat 容器级组件必须由 Tomcat 的 CommonClassLoader 加载如果错放到应用的 WEB-INF/libTomcat 在初始化 Manager 时根本找不到类直接启动失败。cp *.jar $TOMCAT_HOME/lib/ ls -l $TOMCAT_HOME/lib/ | grep -E redis|jedis|pool2复制完成后用 ls 确认三个文件都在。然后检查两件事第一文件的属主和权限Tomcat 的运行用户能不能读第二lib 目录里有没有 commons-pool 1.x 的历史遗留有的话建议移走。commons-pool 1.x 和 commons-pool2 的包名完全不同不会立刻冲突但会在你排查问题时造成严重干扰因为很多老项目的公共 lib 目录里都会带着它。把 1.x 清掉让 lib 目录里只保留当前需要的一套池实现后面所有问题都干净很多。如果 Tomcat 是以 systemd 或服务方式启动的改完 lib 后别忘了重启进程。有些环境还做了 catalina.out 日志轮转重启后先看日志前 50 行能发现类加载阶段的报错。我看到很多团队在这里翻车文件确实复制过去了但启动脚本指定的 CATALINA_HOME 和实际 Tomcat 目录不是同一个复制到了 A 目录启动却去读 B 目录。这时可以用 lsof 或 ps 确认实际进程使用的路径ps -ef | grep catalina | grep -v grep这条命令输出进程参数里面会明确写着 CATALINA_HOME 和 CATALINA_BASE 的指向。对比一下你复制 jar 的路径问题立刻就暴露了。4. 配置 redis 与 web.xml让 Session 真正落到 Redis4.1 context.xml 里的 Manager 配置与参数说明jar 放好之后下一步是把 Session 管理器从默认的 StandardManager 换成 Redis 实现。这个配置写在 Tomcat 的 conf/context.xml 里作用于全局所有应用如果你只想对某个应用启用可以把配置写到那个应用的 META-INF/context.xml 里。两种方式我都用过更推荐放在应用自己的 context.xml 里这样应用移植时配置跟着走不会污染同一个 Tomcat 上的其他应用。Context Valve className完整类名.RedisSessionHandlerValve / Manager className完整类名.RedisSessionManager host127.0.0.1 port6379 database0 password你的Redis密码 maxInactiveInterval60 maxTotal20 maxIdle5 minIdle1 / /Context这里我把类名写成了占位符 完整类名因为不同维护分支的包名前缀不一样下载到的 jar 里是哪个类以 jar tf 的输出为准。思路是固定的一个 Valve 负责在请求结束时把 Session 写回 Redis一个 Manager 负责管理 Session 生命周期和连接池。替换逻辑说明Valve 不是必选但在大多数分支中它是把 Session 变化实时同步到 Redis 的关键缺少它时数据只会在 Manager 的后台线程触发持久化延迟较高进程异常退出时数据容易丢。Manager 的各项参数直接映射到底层 Jedis 连接池。参数说明host 和 port 指向你的 Redis 服务端生产环境不要用 127.0.0.1除非 Redis 确实和 Tomcat 在同一台机器且只能本地访问password 留空表示无密码线上必须配置database 是 Redis 的 015 号逻辑库建议把 Session 单独放在一个库里便于用 flushdb 做整体清理时不影响业务缓存maxInactiveInterval 是 Session 过期秒数这里设 60 表示 60 秒无操作就过期这个值优先级高于 web.xml 里的 session-timeoutmaxTotal、maxIdle、minIdle 分别是连接池的最大连接数、最大空闲连接数、最小空闲连接数单机规模下给 20/5/1 是合理的起点。4.2 web.xml 里必须加的两个 listener配置了 Manager 之后应用的 web.xml 里通常还要加 listener这一步很多教程不展开但漏掉的后果很隐蔽Session 能写入 Redis但过期后 Redis 里的 key 不会被主动清理或者 Session 属性变更时 Redis 里的副本没有同步。listener 的作用就是让 Tomcat 在 Session 创建、销毁、属性变更时通知到 manager。listener listener-class完整类名.RedisSessionListener/listener-class /listener这个 listener 必须在应用自己的 web.xml 里配置而不是放在 Tomcat 的全局配置中。因为不同应用可能有不同的会话有效时长和序列化需求Tomcat 全局配置没法覆盖到每个应用的 listener 加载点。逻辑说明listener 类名同样以你下载的 jar 为准大多数分支会提供至少一个集成好的 listener声明后 Tomcat 在 Session 生命周期事件发生时回调它。如果你的应用已经有自己的 HttpSessionListener两者并不冲突它们会按序执行。修改完 web.xml 后要重新加载应用或重启 Tomcat。验证 listener 是否生效有个讨巧办法启动后登录一个用户然后用 redis-cli 查看 Session key 的 TTL如果 key 存在但 TTL 是 -1说明 listener 没有设置过期时间或者 Manager 的 maxInactiveInterval 没有传递到 Redis。这里我踩过一次listener 加在了 context.xml 里而不是 web.xml结果 Tomcat 根本没去读Session key 全部永久存活Redis 内存两小时就涨到报警线。4.3 序列化策略Java 原生还是 JSONSession 要写进 Redis就一定涉及序列化。大部分分支的默认策略是 Java 原生序列化也就是把你 Session 里的对象通过 ObjectOutputStream 转成字节数组存进 Redis。默认策略的好处是零配置凡是实现 java.io.Serializable 的对象都能直接存坏处是序列化后的内容对跨语言不友好而且对象结构变更后老数据无法读取。另一个常见策略是 JSON 序列化好处是 Redis 里看到的是可读文本适合排查问题坏处是写入性能略差并且复杂嵌套对象、泛型擦除都容易让反序列化失败。对比项Java 原生序列化JSON 序列化配置成本无需要指定序列化器存储可读性二进制不可读文本可读跨语言访问不行可以对象要求必须 implements Serializable要能配合反序列化器性能较快略慢选型建议第一次接入时先用默认的 Java 序列化把整条链路跑通确认业务对象都能正常存取。然后再评估是否有必要切 JSON。很多团队一上来就想切 JSON结果遇到 Session 里存了自定义对象、List 嵌套 Map 等复杂结构反序列化时抛各种类型转换异常排查成本远大于收益。如果你确实需要跨语言访问 Session那也要先明确 Session 里放的对象种类只把简单对象放进 Session复杂对象走数据库。4.4 Redis 侧的准备密码、持久化与内存策略配置完 Tomcat 还不够Redis 服务端自己也需要做一些准备。首先是绑定与密码生产环境不要用默认的 protected-mode要设置 requirepass并在 Tomcat 的 context.xml 里填上同一份密码bind 配置只监听内网网卡不要把 6379 暴露到公网。其次要考虑持久化Session 数据是重要的在线状态如果 Redis 宕机重启后数据全空所有用户都会被强制下线。常见做法是开启 RDB 快照配合 AOF 更稳但 I/O 开销更高。我个人对 Session 场景通常会保持 RDB AOF 双开因为每次 Session 写入其实并不大AOF 追加写对 Redis 性能影响有限。内存策略是另一个容易被忽视的点。Redis 的 maxmemory-policy 如果设置成 allkeys-lruSession key 可能因为内存压力被当作冷数据淘汰用户突然掉线。正确做法是把 Session 单独放到一个 database并设置 maxmemory-policy 为 volatile-lru也就是只淘汰带 TTL 的 key而 Session key 就是带 TTL 的再配合 Session 的过期时间内存压力能通过淘汰机制自动释放。配置后重启 Redis再用 redis-cli 确认参数生效redis-cli -a 你的密码 CONFIG GET maxmemory-policy这条命令返回当前的内存淘汰策略。看到 volatile-lru 后再检查一下 Config 里的 save 策略是否存在确认 RDB 持久化没有被关闭。到这里Redis 侧的准备才算完整Tomcat 那边才敢放心接入。5. 落地过程中的避坑与排查现象、原因、解决5.1 现象Tomcat 启动报 NoClassDefFoundError这是接入时概率最高的启动失败。日志里出现 java.lang.NoClassDefFoundError: redis/clients/jedis/Jedis或者 org/apache/commons/pool2/impl/GenericObjectPool。原因绝大多数是 jar 没放齐或本体 jar 里没有 jedis、commons-pool2 的类而你又没有另外补充。解决方法是回到第 3 章重新检查 lib 目录下是否同时存在三个核心 jar并且版本互相兼容。用 jar tf 看一眼本体内部确认它是否编译依赖外部 jedisjar tf tomcat-redis-session-manager-版本.jar | grep -c redis/clients/jedis输出的数字如果为 0说明本体不带 jedis必须显式放置。如果为 1说明它已经内置。部署时数字为 0 的场景远比为 1 的多所以我的固定动作是放三个而不是放一个。另外启动报错时不要只看 stack trace 的第一行要往下翻Tomcat 经常会先报 LifecycleException再层层剥出 NoClassDefFoundError。定位到具体类名后用 find 在 Tomcat lib 里确认这个类在不在find $TOMCAT_HOME/lib -name *.jar | xargs -I{} sh -c jar tf {} 2/dev/null | grep -l jedis/Jedis.class echo {}这条命令遍历 lib 下所有 jar找出谁包含 Jedis 类。看到输出后你就知道类在哪个 jar 里缺什么补什么而不是盲目重装。5.2 现象Session 写不进 Redis连接被拒Tomcat 能正常启动但请求进来后页面报 JedisConnectionException: java.net.ConnectException: Connection refused。原因有三类Redis 进程没起来、host 写的是 127.0.0.1 但 Tomcat 在另一台机器、Redis 只绑定了本机回环地址。排查顺序先本机后网络redis-cli -h Redis主机 -p 6379 ping返回 PONG 说明网络通服务正常返回 Connection refused 说明端口不通再查防火墙和 bind 配置。这里有个很容易忽略的细节Redis 默认 bind 127.0.0.1即使你 Tomcat 配置里的 host 写的是内网 IP也连不上因为服务端只听本地。解决方法是修改 Redis 配置里的 bind 字段增加内网 IP然后重启 Redis。权限问题上Redis 设置了 requirepass 后客户端必须显式传密码Tomcat 的 context.xml 里 password 没填或填错会报 NOAUTH Authentication required。这个报错通常紧跟着连接成功的日志很难让人第一时间想到密码我遇到过两次都是因为密码里有特殊字符在 XML 里没有转义。XML 中密码包含 或 时必须写成 或 这一条属于配置层面的常见坑。5.3 现象Session 能写但重启后失效Redis 里能看到 Session key应用重启或 Tomcat 重启后这些 key 还在但用户还是全部掉线。这类问题的症状很迷惑人因为数据确实存在 Redis说明共享链路是通的。真正的原因往往出现在 Session 的 key 名上Tomcat 每次重启后生成新的随机值作为 sessionId 的一部分前缀或者 Manager 配置里的 key 前缀不一致导致新启动的节点读不到旧 key。另一种可能性是 browser 端的 Cookie 没有持久化浏览器关掉后 JSESSIONID 丢失这跟 Tomcat 无关。排查技巧是用 redis-cli 查看启动前后 key 的命名结构redis-cli -a 你的密码 --scan --pattern *session*如果重启前后的 key 前缀变化了说明 Manager 配置里的前缀字段没有固定下来。常见做法是把 Session 的 key 前缀明确配置成一个固定字符串比如myapp:session:并在所有节点保持一致。另外一种更常见的原因让人哭笑不得应用部署时用了新版本包Session 里的对象类结构变了反序列化失败后 Manager 静默创建一个新的 Session用户表现为被登出。解决方法是做好对象序列化兼容或把 Session 里的存储对象设计成独立 DTO不要直接存业务实体。5.4 现象Redis 内存暴涨Session 永不过期Redis 的 used_memory 持续上升Session key 数量只增不减。直接原因有三Manager 的 maxInactiveInterval 没生效、web.xml 里 session-timeout 设成 0 或 -1、Redis 里的 key 没有 TTL。三者最容易被忽视的是 session-timeout 的语义Tomcat 里 timeout 设置为 0 表示立即过期-1 表示永不超时如果你的 web.xml 里有类似配置Session 在 Redis 里就是一个永久 key。验证方法很简单随便取一个 Session key 看它的剩余时间redis-cli -a 你的密码 TTL 一个SessionKey返回 -1 表示没有过期时间-2 表示 key 不存在。看到 -1 就在代码和配置里找谁把过期时间设成了永久。还有一个背景Redis 的 key 虽然带了 TTL但如果 maxmemory-policy 是 noeviction内存满了后写入直接报 OOM command not allowed when used memory而不是淘汰旧 key。这时页面会突然大量报错Redis 日志里写着 Memory allocation failed。解决方法是把 maxmemory-policy 调整为 volatile-lru让 Redis 在内存压力下优先淘汰带 TTL 的 Session key。这个问题不是 tomcat-redis-session-manager 本身的问题但接入后 Redis 被填满的锅一定会算到它头上提前配好比事后解释省心得多。5.5 现象分布式应用拿到不同 Session多台 Tomcat 节点配置完成后Nginx 轮询环境下仍然出现不同节点拿到不同 Session的现象。排查第一步先确认 Nginx 是否真的把带有 JSESSIONID 的请求转发到了多个节点如果只有一台节点被命中问题表现和单机一样容易被误判。确认分流正常后再去对比两台 Tomcat 的 context.xml。diff (cat /opt/tomcat1/conf/context.xml) (cat /opt/tomcat2/conf/context.xml)这条命令把两个文件的内容做逐行比较。没有输出说明配置完全一致有输出就请先改配置不要碰王经。常见差异是 database 参数不一致节点 A 连的是 Redis 0 号库节点 B 连的是 1 号库数据自然互不可见。另一个差异是 host 不同一个写的是内网 IP另一个写的 localhost连接到了不同的 Redis 实例或不同端口。还有少数情况是两套 Tomcat 加载了不同版本的 session-manager 本体 jar导致 key 的序列化方式不同这类问题从配置上根本看不出来只能用 redis-cli monitor 实时观察写入来源redis-cli -a 你的密码 monitormonitor 会实时打印所有请求你用另一个终端访问应用触发 Session 写入观察是哪个来源 IP 在写、用的是哪个库、写的 key 叫什么。对比两台节点写入的 key 结构答案立刻就会浮出来。6. 最后一步验证 Session 是否真的进了 Redis以及三个值得做的优化6.1 用 redis-cli 直接查看 Session key配置全部完成后不能只看 Tomcat 启动成功就宣布接入完毕。我习惯做一次完整的端到端验证启动 Tomcat用浏览器访问应用完成一次登录然后回到 Redis 里查redis-cli -a 你的密码 keys *测试环境 keys * 可以接受生产环境不要用会有阻塞风险用 --scan 替代。这时能看到 Session 相关的 key 出现key 的命名会因为分支不同而有差异但大多带 session 片段。之后再用 TTL 命令确认它有过期时间用 type 命令确认它的存储类型redis-cli -a 你的密码 type SessionKey如果返回 hash说明 Session 里的属性被分字段存储如果返回 string说明它被整体序列化了。看到 key 能写、有 TTL、类型正确再把集群里的另一台 Tomcat 也打开用同一个浏览器 Cookie 去访问确认第二次请求没有重新触发登录。这一套验证走完整个链路才算真正落地。6.2 三个值得做的优化第一个优化是固定 key 前缀并规划 Redis 逻辑库。把 Session 的 key 统一加上应用名前缀再单独分配一个 database这样多应用共用一个 Redis 时互不干扰日常巡检也能用一条 scan 命令单独统计会话规模。第二个优化是连接池参数调优。压测时使用 redis-cli info clients 观察 connected_clients如果连接数长期顶着 maxTotal不要急着加连接数先查业务代码里是否有在请求过程中频繁往 Session 写大对象的操作单次写入体积大连接占用时间长加连接只是掩盖问题。第三个优化是给 Redis 的 Session 库设置合理的持久化策略RDB 至少要保留最近一次快照AOF 刷盘策略用 everysec不要使用 always否则写入性能会明显下降。我在这套方案上翻过几次车印象最深的一次是只往 Tomcat lib 里扔了一个本体 jar启动直接报类找不到补齐依赖后又因为 web.xml 漏加 listenerRedis 里的 key 全部永不超时内存报警短信连轰一晚上。现在每次接入前我都会先把依赖清单写下来对照 jar 包实际内容核一遍再动配置。记住这套东西的价值在集群共享不在单机配好之后记得用两个节点交叉验证而不是只在本地 Tomcat 上跑通就收工。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询