基于redis的分布式session共享管理之销毁事件不生效问题
一、前言
首先介绍下分布式session共享管理
在Springboot项目中,经常提到分布式的概念,当实际部署应用后,多台服务器各自存储用户登录会话无法共享,导致操作A按钮还是正常,操作B按钮就提示登录过期需要重新登录。
这是因为接口分发将请求的sessionID传入了不同服务器,未使用分布式session管理时只能在其中一台服务器记录登录信息,所以另一台机器收到请求sessionID识别为未登录出现异常。
所以,此时需要将用户会话信息存储在中间件中,不依赖特定服务器,这样就可以实现各服务器之间共享读取中间件中用户的会话,不会出现上述问题
二、问题
监听session情况,创建、销毁进行回调代码如下
@Configuration
@Slf4j(topic = "login")
public class SpringSessionConfiguration implements HttpSessionListener {@Overridepublic void sessionCreated(HttpSessionEvent se) {log.info("Session 创建: " + se.getSession().getId());}@Overridepublic void sessionDestroyed(HttpSessionEvent se) {log.info("Session 销毁: " + se.getSession().getId());}}
通过debug打断点发现, 不触发根本原因 :
session失效进入了SessionRepositoryFilter类,没有进入StandardSessionFacade导致的
我们来详细分析这个问题的 原因 和 解决方案。
🧠 一、问题现象
- 你实现了
HttpSessionListener
,并注册为 Spring Bean。 sessionCreated()
方法可以正常触发。- 但
sessionDestroyed()
方法不执行,即使 Session 已经过期或被删除。
🔍 二、根本原因分析
1. Spring Session 的 Session 生命周期管理机制
Spring Session(尤其是 spring-session-data-redis
)是通过 SessionRepositoryFilter
来拦截请求并管理 Session 生命周期的。
- 当用户访问时,
SessionRepositoryFilter
会创建一个包装过的HttpSession
(通常是MapSession
或RedisSession
)。 - 这个包装过的
HttpSession
并不是原生的 TomcatStandardSessionFacade
,而是 Spring 自己实现的抽象层。
2. sessionDestroyed()
不触发的原因
HttpSessionListener.sessionDestroyed()
是 由 Servlet 容器(如 Tomcat)触发的,只有当 Session 是由 Tomcat 管理的原生 Session 时才会触发。
而使用 Spring Session 后:
- Session 是由 Spring Session 管理的,不是由 Tomcat 创建的。
- Session 的销毁是通过 Spring Session 内部机制完成的(比如 Redis Key 过期、主动调用
session.invalidate()
)。 - Tomcat 不知道 Session 被销毁了,因此不会触发
sessionDestroyed()
回调。
3. Session 过期后去哪了?
Spring Session 的 Session 过期通常有以下几种方式:
- Redis 中 Session Key 的 TTL 到期,被 Redis 自动删除(被动过期)。
- Spring Session 内部定时任务或请求时检查过期(主动过期)。
- 但这些方式都不会触发
HttpSessionListener.sessionDestroyed()
,因为不是 Tomcat 触发的。
三、方案
✅ 方案一:监听 Spring Session 的 SessionDestroyedEvent
Spring Session 提供了它自己的事件机制,你可以监听 SessionDestroyedEvent
来替代 HttpSessionListener
。
@Component
@Slf4j
public class SessionEventListener {@EventListenerpublic void handleSessionCreated(SessionCreatedEvent event) {log.info("Session 创建: " + event.getSession().getId());}@EventListenerpublic void handleSessionDestroyed(SessionDestroyedEvent event) {log.info("Session 销毁: " + event.getSessionId());}
}
✅ 方案二:启用 Redis Key 过期事件 + 监听 Redis 的 expired
事件
如果你的 Session 是被 Redis 主动删除的(TTL 过期),Spring Session 默认不会感知到。
你需要:
配置文件中搜索开启,重启后生效
notify-keyspace-events Ex
四、总结
经过笔者测试使用方案二实现最终效果,当会话过期自动调用销毁方法,整个过程还是比较费劲,一边结合AI分析,一边查阅资料自己打断点推敲,事实证明完全依赖AI无法解决实际问题,还需要人脑参与统筹分析,也劝各位读者不要过分依赖AI。