尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Tomcat 8集群Session共享:Redis会话管理配置与排错
简介面向Tomcat8与JDK1.8环境下需要实现分布式会话共享的Java开发者这份包集合提供将Redis接入Tomcat Session管理的依赖与配置参考帮助解决多节点部署中会话无法共享、内存存储效率有限的问题。压缩包共6个文件以4个jar为主另含1个xml配置示例和1个txt说明整体约445KB便于直接放入Tomcat lib目录或纳入项目依赖体系。jar文件覆盖会话管理器、Redis客户端及连接池等运行组件xml与txt则提供配置和使用线索适合在集群、负载均衡场景中验证会话一致性。目前已有940人学习说明该方向具有稳定的实践需求。读者可快速集齐会话共享所需包体减少逐项查找依赖与版本匹配的时间并借助配置示例理解序列化、连接参数与存储位置的设置思路为测试和性能调优打下基础。1. 单机跑得好好的登录态一上集群就散架这套包到底解决什么单机 Tomcat 跑得好好的应用前面一挂负载均衡、后面扩到两台登录态就开始奇怪——刷新一下掉线、下单到一半跳登录页、后台看到 A 节点有会话、B 节点查不到。根源不在业务代码Tomcat 默认的StandardManager只把会话写在当前 JVM 堆里进程一换就归零。tomcat-redis-session-manager 这套包集合干的就是这件事把会话的公共存储换成 Redis让集群里任意一台 Tomcat 8 都能读到同一份 Session过期、序列化、并发读写的细节全部交给 Manager 统一托管。它适合已经上到 Tomcat 8、正在做无状态化迁移、又不想立刻把会话整体迁到别的会话框架上的团队也适合手里只有一台机器想先把“会话外置”这条路完整跑通再谈扩容的人。2. 拆包与版本对齐tomcat8 下这几个 jar 一个都不能错位拿到包集合第一步不是改配置是把里面的东西认全。很多“配了不生效”的现场最后查下来不是参数写错而是某个 jar 根本没被 Tomcat 的类加载器看见或者版本对不上导致 Manager 在启动阶段就抛NoClassDefFoundError被吞掉。这一章先把文件职责、版本边界和落盘位置捋清楚后面配context.xml才有意义。2.1 包集合里每个文件分别管哪一段一个能用的 tomcat-redis-session-manager 包集合核心就是三层会话管理器本身、Redis 客户端、连接池。三者缺一不可而且必须放在同一个类加载层级里否则运行到第一次写会话才会报错排查起来很被动。文件职责正确落位缺失或错位时的表现会话管理器主包RedisSessionManager 实现实现 Tomcat 的 Manager 接口接管会话的创建、查找、写回、过期$CATALINA_HOME/libcontext.xml里 className 解析失败Tomcat 启动日志报ClassNotFoundExceptionManager 静默回退到默认实现Redis 客户端包提供 Jedis 连接与命令封装$CATALINA_HOME/lib首次写会话时报NoClassDefFoundError: redis/clients/jedis/Jedis连接池包提供连接复用、空闲回收、借还超时$CATALINA_HOME/lib启动阶段报NoClassDefFoundError: org/apache/commons/pool2/...或运行几分钟后连接耗尽配置样例 / 源码参考用不参与运行不部署无影响判断主包里的 Manager 实现类全限定名最稳的办法是直接翻 MANIFEST 或者解包看目录结构不要凭记忆抄# 列出包里所有带 SessionManager 字样的类确认实现类的真实包路径 jar tf redis-session-manager-tomcat8.jar | grep -iE SessionManager|Manager.class # 顺便把 MANIFEST 打出来看 Build-Jdk、Implementation-Version 这些线索 unzip -p redis-session-manager-tomcat8.jar META-INF/MANIFEST.MF第一条命令输出的是类文件路径把/换成.、去掉.class就是全限定名。示例里我统一写成com.example.tomcat.redissessions.RedisSessionManager你按自己包里的实际值替换。第二条命令用来确认打包时的 JDK 版本如果是用 JDK 11 编译、而线上跑的是 JDK 8启动会直接报UnsupportedClassVersionError这个错误在catalina.out里很显眼但很多人第一眼会以为是 Tomcat 版本问题。2.2 Tomcat 7 的包为什么不能直接丢进 Tomcat 8这是最容易翻车的一处。会话管理器是贴着 Tomcat 内部 API 写的它要继承ManagerBase、要操作StandardSession、要自己生成 session id这些类在 Tomcat 7、8.0、8.5、9 之间的方法签名和可见性都动过。具体来说Tomcat 8 把会话 id 的生成逻辑往SessionIdGenerator上收ManagerBase里若干 protected 方法的参数列表和 Tomcat 7 不一致StandardSession内部对access、isValid、expire的处理顺序也调整过。用 Tomcat 7 编译出来的包放到 Tomcat 8 上编译期没问题运行期会在第一次createSession时抛NoSuchMethodError或者AbstractMethodError——这两个异常的迷惑性在于堆栈里看不到业务代码全在 Tomcat 的 valve 链里。血泪经验是主包和 Tomcat 的对应关系必须精确到次版本。8.0.x 和 8.5.x 在会话这块不是一个实现8.0 的包放 8.5 上大概率能启动、能读到已有会话但新建会话时莫名其妙不落库反过来 8.5 的包放 8.0 上可能连启动都过不去。所以拿到包集合先看一眼目录名或 MANIFEST 里标的 Tomcat 版本对不上就别硬试换包比调参省时间。还有一点常被忽略Tomcat 8 的$CATALINA_HOME/lib里自带tomcat-dbcp.jar它内部 shaded 了一份连接池实现但包名是org.apache.tomcat.dbcp.pool2和标准的org.apache.commons.pool2不冲突。所以连接池包可以放心放进去不需要为了避让去改包名。真正要小心的是项目WEB-INF/lib里如果也带了一份老版本连接池双份加载会在运行期出现“池子明明没满却借不到连接”的玄学现象。2.3 为什么必须落在 $CATALINA_HOME/lib 而不是 WEB-INF/libTomcat 的类加载是分层的容器自己的类包括Manager、StandardSession由 common classloader 加载Web 应用由 webapp classloader 加载后者能看到前者前者看不到后者。context.xml里的Manager节点是在容器启动、解析 Context 的时候实例化的这个时机 webapp classloader 可能还没建立完成所以 Manager 实现类必须放在容器可见的路径下。放在WEB-INF/lib的典型症状是context.xml里 className 已经写对Tomcat 启动日志里却出现ClassNotFoundException然后自动回退到StandardManager——注意是“回退”不是“启动失败”。也就是说服务照常起来业务照常能跑只是会话又回到了单机内存模式你不做多节点验证根本发现不了。落盘步骤我一般是这样走的先备份再覆盖出问题能一分钟回滚# 1) 停服务确认进程真的退了避免文件被占用或新旧类混跑 $CATALINA_HOME/bin/shutdown.sh sleep 3 ps -ef | grep -v grep | grep catalina # 2) 备份现有 lib 目录只备份不删除 cd $CATALINA_HOME tar -czf lib-backup-$(date %Y%m%d%H%M).tar.gz lib/ # 3) 把三个包拷进去注意权限要和目录里其它 jar 一致 cp /path/to/pkg/redis-session-manager-tomcat8.jar $CATALINA_HOME/lib/ cp /path/to/pkg/jedis-*.jar $CATALINA_HOME/lib/ cp /path/to/pkg/commons-pool2-*.jar $CATALINA_HOME/lib/ chmod 644 $CATALINA_HOME/lib/redis-session-manager-tomcat8.jar # 4) 检查有没有同名的旧版本残留版本冲突基本都藏在这一步 ls -l $CATALINA_HOME/lib/ | grep -iE jedis|pool2|session第三步的chmod 644不是形式主义。用不同账号上传文件时jar 权限可能变成 600Tomcat 若以非 root 账号运行就读不到症状同样是ClassNotFoundException但你去ls看文件明明在那儿能查很久。第四步是重点jedis和commons-pool2只要出现两个不同版本的 jar类加载顺序不确定运行期报的错会非常随机。落位完成后别急着配context.xml先做一次“类可见性自检”用 Tomcat 自带的启动参数把类路径打出来确认 jar 真的进了 common classloader# 用 catalina.sh 的调试模式启动只看类路径那一行看到就 CtrlC $CATALINA_HOME/bin/catalina.sh run -verbose:class 21 | grep -i redissession如果这条命令没有任何输出说明 jar 没被扫到先回头查权限和文件名别往下走。这一步花两分钟能省掉后面半小时的“为什么 Manager 没生效”。3. context.xml 里的 Manager 节点host、连接池和持久化策略怎么配对jar 落位只是让类能被找到真正决定行为的是context.xml里那段Manager配置。这部分参数不多但相互之间有约束关系——timeout、maxWait、maxInactiveInterval三个值配反了表现出来就是“会话偶尔丢”“高并发下登录态抖动”这类很难复现的问题。3.1 配在哪一层全局 context.xml 还是单应用Tomcat 的 Context 配置有三个位置作用域从大到小位置作用范围适用场景$CATALINA_HOME/conf/context.xml该 Tomcat 实例下所有 Web 应用一台机器只跑一个业务应用或所有应用共用同一套 Redis$CATALINA_HOME/conf/Catalina/localhost/应用名.xml指定应用多应用部署但要分别指向不同 Redis 库推荐这种方式应用包内META-INF/context.xml仅该应用配置跟着制品走但要确认 Tomcat 没开copyXML否则会被复制到 conf 下覆盖我一般选第二种。原因很实际conf/context.xml是全局的一旦某天在这台机器上加了一个管理后台之类的附属应用它会莫名其妙也去连业务 Redis把会话写进同一个库而放在应用包里的配置重新部署 war 时如果 Tomcat 开了copyXMLconf 下的副本优先级更高改了包里的不生效很容易怀疑人生。3.2 属性逐项对照不同分支的包属性名可能略有差异但下面这些是通用的一组配之前对着包里 README 核一遍名字。属性示例值说明配错的后果className包内实际全限定名Manager 实现类写错则回退默认实现会话不落 Redishost10.0.0.21Redis 地址建议写内网 IP不要写localhost之外的域名避免 DNS 抖动port6379Redis 端口—database0库编号多应用共用同库会 key 冲突见第 5 章password空或实际密码认证口令空密码写成不要漏写这个属性timeout2000Redis socket 读写超时毫秒设太大Redis 卡顿时请求线程全挂住maxActive200连接池最大连接数小于并发峰值时出现借连接超时maxIdle50最大空闲连接太小会频繁创建销毁连接minIdle10最小空闲连接设 0 时流量突增会有冷启动延迟maxWait2000借连接最长等待毫秒必须小于timeout否则等连接的时间比 Redis 超时还长maxInactiveInterval1800会话空闲过期秒和 Redis TTL 不一致会导致“Redis 里还有、Tomcat 认为已过期”sessionPersistPersistencePolicyALWAYS写回策略常见取值为每次请求都写或仅变更时写取“仅变更”时某些框架改的是对象内部状态而非 setAttribute不会被判定为变更3.3 一份能直接改地址就用的最小配置?xml version1.0 encodingutf-8? Context !-- className 用 2.1 节从 jar 里查出来的真实全限定名替换 -- Manager classNamecom.example.tomcat.redissessions.RedisSessionManager host10.0.0.21 port6379 database3 password timeout2000 maxActive200 maxIdle50 minIdle10 maxWait2000 maxInactiveInterval1800 sessionPersistPersistencePolicyALWAYS/ /Context这段配置里有两个值需要结合业务改。database我用了3而不是默认的0因为同一套 Redis 往往还跑着缓存、队列会话单独占一个库出问题时SELECT 3进去看不会和业务 key 混在一起。maxInactiveInterval写成 1800 秒意味着用户半小时不操作就要重新登录这个值要和产品确认不要照抄。如果你的包版本用的是嵌套连接池节点写法把上面四个池参数挪进子节点即可其余属性仍挂在Manager上Manager classNamecom.example.tomcat.redissessions.RedisSessionManager host10.0.0.21 port6379 database3 password JedisPoolConfig maxTotal200 maxIdle50 minIdle10 maxWaitMillis2000/ /Manager两种写法不要混用。我见过同一份配置里maxActive和maxTotal同时写了两个不同值最后生效的是哪个取决于解析顺序压测时连接数怎么都对不上预期。3.4 timeout、maxWait、maxInactiveInterval 三个值的推导关系这三个值不是各调各的它们串在一条时间链上maxWait借连接等待必须小于timeoutRedis 读写超时。否则一次请求可能先花 3 秒等连接再花 2 秒等 Redis加起来 5 秒还没返回前端网关早就超时了用户看到的是白屏而不是“慢”。timeout加上maxWait的总和要小于 Nginx 或网关的proxy_read_timeout。常见网关默认 60 秒看起来余量很大但 Tomcat 的请求线程是有限的默认 200一旦有几十个线程卡在 Redis 上后面的请求就排不上队表现出来是“整个站点卡住”而不是“某个接口慢”。maxInactiveInterval决定会话在 Tomcat 侧什么时候失效而 Redis 侧的 key 也有 TTL。理想状态是两者一致让 Redis 自动清理。如果 Redis TTL 比maxInactiveInterval短会出现“用户还在操作、会话突然没了”反过来 TTL 更长Redis 里会堆积大量已经没人访问的会话 key内存缓慢上涨。我一般按maxWait ≤ timeout、timeout maxWait 网关超时 / 2这两条硬约束先卡死再根据压测结果上下微调。保守起点是maxWait2000、timeout2000Redis 在同机房内网时这个值足够跨机房再往上加。配置改完重启别只看“启动成功”四个字。去logs/catalina.out里 grep 一下 Manager 的初始化日志确认实例化的是你配的那个类而不是StandardManagergrep -iE Manager|RedisSession $CATALINA_HOME/logs/catalina.out | tail -20看到 Manager 相关类名出现在日志里才说明配置真的被解析了。这一步不做后面所有验证都是在验证默认实现。4. 序列化边界Session 里塞进去的对象为什么会一存就炸会话外置之后所有放进 Session 的属性都要经过一次序列化才能进 Redis。这一步的失败方式特别隐蔽——不是抛给业务方一个异常而是在 Manager 内部被 catch 掉业务侧只看到“这个属性读出来是 null”。所以序列化这块要提前验不能等联调时靠猜。4.1 会话写进 Redis 的那条链一次request.getSession().setAttribute(user, userObj)在背后走的是StandardSession.setAttribute先更新内存里的属性表并标记 dirty请求结束时 Manager 判断是否需要持久化需要则调用序列化器把整个会话或变更部分打成字节数组再通过 Jedis 写进 Redis 的对应 key。这条链上任何一环失败业务代码都感知不到。序列化器抛NotSerializableExceptionManager 记一条日志请求正常返回用户下一次请求读出来的属性就是 null。所以第 4 章的核心不是“怎么配”而是“怎么在开发阶段把这个错提前暴露出来”。4.2 JDK 序列化的四条硬约束这套包默认走的是 JDK 原生序列化规则很硬对象必须实现java.io.Serializable。注意是对象本身不是它的父类——父类没实现、子类实现了父类的字段不会被序列化反序列化后是默认值。所有非transient的实例字段必须递归可序列化。你的 DTO 里挂了一个java.sql.Connection、一个ThreadLocal、一个 Spring 的ApplicationContext引用整个对象就废了。serialVersionUID必须显式声明。不写的话 JVM 会根据类结构算一个任何一次加字段、改方法签名都会让它变结果是“旧会话反序列化失败”而这个错误只在滚动发布期间出现测试环境单实例根本复现不了。内部类、匿名类、Lambda 捕获的this$0引用是重灾区。一个非静态内部类实例会隐式持有外部类引用外部类不可序列化整个对象就不可序列化——报错信息里只会说内部类不可序列化不会告诉你根因在外部类。4.3 写个探针把可疑对象先过一遍与其等联调时一个个属性试不如在本地写个十几行的小工具把准备放进 Session 的对象全过一遍。原理很简单ObjectOutputStream写一遍ObjectInputStream读回来能走通就是可序列化的。import java.io.*; public class SessionSerialProbe { /** * 把对象完整走一遍 JDK 序列化 反序列化返回反序列化后的副本。 * 走不通会抛异常异常信息会带出具体是哪个字段/哪个类不可序列化。 */ public static Object roundTrip(Object src) throws Exception { if (src null) { return null; } ByteArrayOutputStream bos new ByteArrayOutputStream(); try (ObjectOutputStream oos new ObjectOutputStream(bos)) { oos.writeObject(src); oos.flush(); } byte[] bytes bos.toByteArray(); try (ObjectInputStream ois new ObjectInputStream(new ByteArrayInputStream(bytes))) { return ois.readObject(); } } public static void main(String[] args) { // 换成你实际要放进 Session 的对象 Object[] candidates new Object[]{ new UserDTO(1001L, tester), new java.util.HashMapString, Object() {{ put(role, admin); put(loginAt, System.currentTimeMillis()); }} }; for (Object c : candidates) { String name c.getClass().getName(); try { Object copy roundTrip(c); System.out.println([OK] name - copy); } catch (Exception e) { // 打印完整堆栈根因通常在 cause 链的最里层 System.out.println([FAIL] name); e.printStackTrace(System.out); } } } }两个细节值得说。第一roundTrip返回的是副本而不是原对象这很重要——只有真正写完再读回来才能发现“写的时候没问题、读的时候字段丢失”这类不对称问题。第二main里我特意放了一个匿名内部类构造的HashMap双大括号初始化这类写法会生成一个匿名子类如果你的包路径里有不可序列化的东西它是最早暴露问题的地方之一。UserDTO长这样重点是显式serialVersionUID和所有字段类型都简单import java.io.Serializable; public class UserDTO implements Serializable { // 显式声明任何字段调整都手动改这个值避免滚动发布期间反序列化失败 private static final long serialVersionUID 2024010101L; private Long userId; private String userName; public UserDTO(Long userId, String userName) { this.userId userId; this.userName userName; } Override public String toString() { return UserDTO{userId userId , userName userName }; } }跑一遍探针[OK]的对象才允许进 Session。这一步在开发机上做成本是一分钟放到联调环境做成本是按小时算的因为你要在几十个属性里二分定位。4.4 序列化失败的日志长什么样失败时catalina.out里通常是这样一行SEVERE [http-nio-8080-exec-3] org.apache.catalina.session.ManagerBase ... java.io.NotSerializableException: com.example.service.OrderService关键是最后那行类名。它指向的是“不可序列化的那个字段的类型”不一定是你的 Session 属性本身的类型。比如你把一个 Service 注入到了 DTO 里当字段报的是OrderService但问题在OrderDTO。顺着这个类名往上找谁引用了它比从头 review 代码快得多。提示滚动发布时最容易踩serialVersionUID这个坑。新旧两个版本的类结构只要有一个字段差异旧会话在新实例上就反序列化失败。要么把serialVersionUID写死并保证兼容要么发布时清空该应用的会话 key 让用户重新登录。5. 五条会静默吃掉会话的坑从 jar 位置到过期时间下面这五条都是我在不同环境里真实遇到过的共同点是服务不报错、监控不告警、只有用户投诉“登录状态保不住”。每条按现象、原因、解决三步写遇到了可以按顺序对照。5.1 会话就是不落 Redis业务一切正常现象context.xml配好了服务正常启动登录也正常但redis-cli里一个会话 key 都看不到扩容到两台后跨节点访问立刻掉登录态。原因Manager 实现类没被容器加载到Tomcat 静默回退到了StandardManager。触发条件是 jar 放在了WEB-INF/lib而不是$CATALINA_HOME/lib或者 jar 文件权限是 600 而 Tomcat 以其他账号运行。解决先按 2.3 节的类路径自检确认 jar 可见再 grepcatalina.out里的 Manager 初始化日志确认实例化的是自定义类。两个都通过了才继续往下查配置。5.2 Redis 挂掉时 Tomcat 起不来或起来后疯狂刷异常现象Redis 做一次主从切换Tomcat 重启后卡在启动阶段不返回或者勉强起来了日志里每秒几百条连接异常CPU 被打满。原因Manager 在初始化时会尝试建立连接池并做一次连通性检查Redis 不可达时这一步会阻塞到timeout而请求路径上每次getSession都要读 Redis异常被反复抛出又没有降级逻辑日志量直接把磁盘写满。解决一是把timeout和maxWait调小比如都设 1000让失败快速返回而不是卡住线程二是在接入层对“Redis 不可用”做兜底策略比如允许降级到本机会话代价是这段时间内的跨节点会话不共享——这个决定要和业务确认不能自己拍。监控上给 Redis 连接失败次数单独设告警别等用户投诉才发现。5.3 会话属性偶尔读出来是 null现象用户信息、购物车这类放在 Session 里的对象大部分时候能读到偶尔返回 null刷新一下又好了。原因属性对象在某次写回时序列化失败Manager 吞掉了异常内存里的属性还在所以当前请求正常但 Redis 里存的是上一次成功写入的旧值或者干脆没有。常见触发点是属性对象里挂了一个不可序列化的字段比如某个 ORM 的代理对象、某个带线程本地变量的工具类。解决按 4.3 节的探针在开发阶段全量过一遍线上排查时把 Manager 相关日志级别调到 DEBUG观察写回阶段有没有NotSerializableException。另外把“Session 属性读取为空”在业务侧当成一个显式分支处理别默认它一定有值。5.4 两套应用互相把对方的会话覆盖了现象同一台 Tomcat 上部署了两个应用分别是前台和后台两边登录后互相踢下线或者redis-cli里看到 key 数量比实际在线人数多一倍。原因两个应用都配了database0而会话 key 的命名前缀是固定的没有按应用名区分于是两边的会话写进了同一批 key 里。解决最简单是给每个应用分配不同的database如果 Redis 实例不允许分库就在配置里改会话 key 的前缀让两个应用的前缀不同。改完一定要清一次旧 key否则存量脏数据会继续引发“随机掉线”让人误以为没改对。5.5 用户半小时没操作就被踢但产品说设的是两小时现象maxInactiveInterval明明配的 7200 秒用户反馈半小时左右就要重新登录。原因Redis 侧的 key TTL 由另一处逻辑决定可能沿用了默认的 1800 秒也可能被别的清理脚本按固定周期扫掉了。两边不一致时谁先到期听谁的。解决把maxInactiveInterval和实际写进 Redis 的 TTL 对齐用一次TTL命令验证redis-cli -h 10.0.0.21 -p 6379 -n 3 --scan --pattern tomcat:session:* | head -1 \ | xargs -I{} redis-cli -h 10.0.0.21 -p 6379 -n 3 TTL {}返回值应该接近maxInactiveInterval而不是 1800。如果差得远去查有没有别的定时任务在扫这个库。6. 上线前的会话验证闭环redis-cli 里该看到什么压测时该盯哪几个数配置改完、服务起来很多人就直接上负载均衡了。我的习惯是无论多急上线前必须走一遍下面这个闭环四步十分钟能挡掉九成的会话问题。第一步确认 key 真的写进去了。用SCAN而不是KEYS前者不会阻塞 Redis# 用 SCAN 游标遍历生产环境也不怕 redis-cli -h 10.0.0.21 -p 6379 -n 3 SCAN 0 MATCH tomcat:session:* COUNT 100正常应该能看到一批 key。一个都没有回到第 5.1 节有 key 但数量远小于在线人数回到第 5.3 节。第二步确认 TTL 和配置一致# 拿上一步返回的任意一个 key看剩余存活时间 redis-cli -h 10.0.0.21 -p 6379 -n 3 TTL tomcat:session:你的sessionid返回值应该接近maxInactiveInterval减去已经过去的时间。如果是 -1永不过期或者个位数说明过期策略没配上长时间运行会撑爆 Redis 内存。第三步做一次真正的跨节点验证。这一步不能靠“看起来正常”要用 curl 手工带 cookie# 第一个请求打到 A 节点拿到 JSESSIONID curl -i -c /tmp/cookie.txt http://10.0.0.31:8080/app/login # 第二个请求打到 B 节点带上同一个 cookie看是否还是登录态 curl -i -b /tmp/cookie.txt http://10.0.0.32:8080/app/profile如果第二个请求返回 302 跳登录页说明会话没共享成功。这时候不要怀疑配置先检查两边的database、key 前缀、maxInactiveInterval是否完全一致——三个值里任何一个不同都会导致这个结果。第四步压测时盯三个数。第一个是 Redis 的连接数redis-cli info clients里的connected_clients它应该稳定在maxActive附近而不是持续爬升持续爬升说明连接没被归还通常是某条异常路径漏了close。第二个是 Tomcat 的请求线程数/manager/status或者 JMX 里看 busy threads如果长时间接近上限大概率是timeout设太大导致线程堆积。第三个是会话 key 的增长率用两次SCAN间隔一分钟对比数量如果在线用户数稳定但 key 数持续上涨说明过期没生效或者每次请求都新建了会话。关于压测脚本我一般不用现成的工具直接打而是写个小脚本控制“同一批用户反复访问不同节点”这个模式因为会话问题只有在跨节点、且 cookie 复用的场景下才会暴露。单节点压测再高的 QPS 也验不出会话共享有没有生效。最后说一个我自己的教训。早年做一次扩容配置都改好了压测也过了上线第二天开始零星有用户投诉“下单页面跳登录”。查了两天才发现是灰度期间新旧实例的serialVersionUID不一致旧实例写进去的会话新实例读不出来而灰度流量只有 5%所以压测阶段完全没暴露。从那以后我每次滚动发布都会在发布前把该应用的会话 key 前缀单独列出来确认新旧版本的 DTO 结构兼容不兼容就先清空再发。这个动作花不了一分钟但能挡掉一整类只在灰度窗口出现的诡异掉线。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

Zabbix交换机SNMP监控模板实战:OID适配与故障排查

Zabbix交换机SNMP监控模板实战:OID适配与故障排查

简介:本资源是一套专为Zabbix监控系统设计的交换机性能监控模板,面向网络运维工程师、系统管理员及IT监控初学者,解决交换机流量、端口状态、错误统计等关键指标的手动配置繁琐问题。压缩包为RAR格式,共2个XML模板文件&#xff0c…

📅 2026/10/23 11:23:26
【自适应核卡尔曼滤波AKKF用于单目标纯方位跟踪】基于再生核希尔伯特空间嵌入的纯方位目标跟踪算法设计与性能分析(Matlab代码实现)

【自适应核卡尔曼滤波AKKF用于单目标纯方位跟踪】基于再生核希尔伯特空间嵌入的纯方位目标跟踪算法设计与性能分析(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 &#x1f381…

📅 2026/10/23 11:23:26
pylib本地安装与模块验证完整指南

pylib本地安装与模块验证完整指南

简介:这是一份面向Python中高级开发者的实用工具库资源,专为提升日常编码效率而设计,通过封装os、sys等内置模块及numpy、tensorflow、cv2等主流库的高频操作,显著降低跨库API差异带来的学习与维护成本。资源包共103个文件&#x…

📅 2026/10/23 11:18:23
MORE NEWS

更多资讯

📰

Colima:Mac上轻量级Docker容器运行时实战指南

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

📰

屏幕翻译工具Translumo实战:OCR与翻译引擎调优指南

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

📰

Java进程终止失败?从端口占用到优雅停机的排查与根治

你正在开发一个Java后端服务,改完代码满怀期待地点击“Rerun”,结果开发工具没有像往常一样直接重启,而是弹出一个英文报错:couldnt terminate the existing process for 8080。接下来不管你怎么点停止按钮,界面上的小…

📰

MATLAB压缩感知图像重建:512×512 Lena图15%采样实现高保真重构

简介:本资源是一套面向数字图像处理、计算机视觉及信号处理方向学习者与研究者的二维图像压缩感知(CS)重构算法MATLAB实现代码,聚焦稀疏信号理论在图像重建中的落地应用,适用于高年级本科生、研究生及工程实践人员理解…

📰

米散射MATLAB实现:从气溶胶到颗粒检测的五个光学参量计算

简介:这份MATLAB程序面向光学、大气科学与遥感领域的学习者与科研人员,提供经典米散射(MIE散射)理论的数值实现,用于计算球形粒子在光照下的散射系数、吸收系数、消光系数、后向散射系数及不对称因子等宏观光学参数。资…

📰

大模型 Flash/Pro 选型、API 接入与生产化调优实战

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

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬