AI 大模型评测平台多实例部署在负载均衡之后,传统容器内存 Session 无法跨节点共享,粘性会话又会在节点下线时丢登录态。用 Spring Session 把会话外部化到 Redis,所有实例读同一份存储,登录、鉴权、登出逻辑零改造,且支持即时吊销。相比 JWT,它在”需要服务端强制失效”的 Web 场景里更省心。下文给出依赖、配置与两者差异。
说明:本文基于通用工程实践对平台会话层做合理推演,非平台真实源码。方案适用于任意 Spring Boot + Spring Session 技术栈,读者应按自身安全基线补充 CSRF 与密码策略。
一、多实例下原生 Session 为什么会失效
单体时代 Session 写在 Tomcat 堆里,请求带上 JSESSIONID Cookie,服务器按 ID 取回会话。上了负载均衡后,同一用户的两次请求可能被分到不同实例,B 实例没有 A 实例的内存会话,于是反复要求重新登录。粘性会话能缓解,但节点宕机或扩容时仍会丢态,且不利于蓝绿发布。
解决思路只有两条:把会话搬到所有实例都能访问的共享存储(Redis),或把状态塞进客户端令牌(JWT)。评测平台是受自己掌控的浏览器 Web 应用,选前者最稳妥。
二、Spring Session + Redis 落地
2.1 引入依赖
spring-session-data-redis 负责会话仓库,spring-boot-starter-data-redis 提供连接,二者配合由 Spring Boot 自动装配。
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.2 一行注解开启
@EnableRedisHttpSession 接管 HttpSession 的实现,把读写重定向到 Redis。应用代码里继续用标准 request.getSession(),无需改动。
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
public class SessionConfig {
// 会话自动存入 Redis,所有实例共享
}
spring:
session:
store-type: redis
data:
redis:
host: ${REDIS_HOST:127.0.0.1}
port: 6379
用户登录后,客户端拿到一个不透明的 32 位 sessionId 写在 HttpOnly、Secure 的 Cookie 里;每个请求 Spring 用这个 ID 去 Redis 取回会话对象,填充 SecurityContext。会话 TTL 由 Redis 自动管理,空闲超时就清理。
三、与 JWT 的对比
| 对比维度 | Spring Session + Redis | JWT |
|---|---|---|
| 状态位置 | 服务端 Redis | 客户端 Token 内 |
| 吊销能力 | 即时,deleteById 即失效 |
无黑名单则要等过期 |
| 横向扩展 | 共享存储,天然支持 | 无状态,天生分布式 |
| 令牌体积 | Cookie 约 32 字节 | 300–600 字节/请求 |
| 代码侵入 | 低,配置即用 | 中高,需自管签发校验 |
| 适用 | 浏览器 Web、需强管控 | 移动端、跨服务、SSO |
JWT 把身份声明塞进签名令牌,服务端不存,校验靠本地验签、无网络往返,适合移动 App 与微服务间互信。代价是吊销难:用户改密、被封号、单设备登出,纯 JWT 在过期前都不会生效,除非再引入 Redis 黑名单——那又把自己绕回了”每次请求查存储”。
四、为什么评测平台更合适用 Session
评测平台是后台运营系统,登录态受平台完全掌控。下列诉求 Session 方案直接满足:
- 管理员改密码后,旧登录态要立刻失效;
- 发现异常账号,运维要在后台一键踢人;
- 多实例滚动发布,用户无感知不掉线;
- 会话里要放较多上下文(当前租户、角色、评测权限),放 Redis 比塞进 JWT 体积小、可改。
这些场景的共性是”服务端要说了算”。用 sessionRepository.deleteById(sessionId) 一行就能吊销,而 JWT 做不到。
@Autowired
private FindByIndexNameSessionRepository<? extends Session> sessionRepository;
public void revoke(String sessionId) {
sessionRepository.deleteById(sessionId); // 即时失效
}
五、安全与高可用注意点
HttpOnly 防 XSS 读 Cookie,Spring Security 默认开启;Secure 保证仅 HTTPS 传输。登录成功后调用 request.changeSessionId() 防会话固定攻击。Redis 存敏感会话,须设访问密码并限制网络暴露;生产用主从或哨兵保证会话存储高可用,否则 Redis 宕机全平台掉登录。
六、混合架构的取舍
并非二选一。评测平台主体用 Session + Redis 管后台登录,若对外提供 OpenAPI 给第三方脚本调用,可对该接口改用 JWT:用户在开放平台换到短期 Access Token,放在 Authorization 头,平台用公钥本地验签,绕过 Cookie 体系。两套机制在同一应用内并存,按端点区分,既能保后台强管控,又能给机器调用者无 Cookie 的便利。
| 端点类型 | 鉴权方案 | 理由 |
|---|---|---|
| 后台管理页 | Session + Redis | 需即时吊销、存多上下文 |
| 开放 API | JWT | 无 Cookie、第三方易集成 |
| 内部服务调用 | JWT / mTLS | 服务间无浏览器环境 |
七、会话体积与序列化
会话里只放必要字段(用户 ID、角色、租户、权限位),别把整张用户表塞进去。Spring Session 默认用 JDK 序列化,体积大且要求类可序列化;改用 JSON 序列化器(如 GenericJackson2JsonRedisSerializer)更小更可读,也便于运维在 Redis 里直接排查会话内容。会话字段变更时需注意反序列化兼容,避免旧会话因结构不匹配而读取失败。敏感字段(如明文权限串)不要进会话,应只存用户 ID,需要时再查库或缓存拼装,降低会话泄露的波及面。
八、迁移与灰度
老系统若已用容器内存 Session,切到 Redis 不必停机。先上 Redis 并保持双写窗口:新会话写 Redis,旧实例内存会话仍可读,待所有活跃会话自然过期(或强制全员重登)后下线双写。灰度期盯三个指标:Redis 会话命中率、平均读取延迟、登录成功率。命中率稳定且延迟低于个位数毫秒,说明外部化成功;若延迟异常,多半是连接池过小或网络跨区,应先调 lettuce 池参数再放量。切流期间保留原内存 Session 兜底一周,万一 Redis 异常可快速回退,不把迁移风险压在单点上。回退开关做成配置项,运维一键切回原生 Session,无需重新发布,把故障恢复时间压到分钟级。
常见问题(FAQ)
Q1:Session 比 JWT 更难扩展吗?
不。会话存 Redis 后所有实例共享,无需粘性会话即可横向扩展。
Q2:JWT 能强制登出吗?
纯 JWT 不能,需额外黑名单;Session 调用 deleteById 即即时失效。
Q3:会话数据变大影响性能吗?
不会明显。Redis 读写为亚毫秒级,且只传 32 字节 sessionId 而非整段数据。