Spring Session 的原理

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 今天在写一个对外接口, 这个接口大致原理是在过滤器中通过 token 获取用户信息然后创建 session, 后续的流程就是 `Controller -> Service -> Dao` 了.这次开发没有像之前那样愣头愣脑的, 我想了一下, 对方调用的时候是没有 session id 的, 也就是每次认证之后都会创建一个 session. 那这就可能存在一个大问题了, 假设调用次数非常多的话, 会创建茫茫多的 session, 可能会击垮系统.

原文地址: Spring Session 的原理

欢迎访问我的博客: https://blog.duhbb.com


引言

今天在写一个对外接口, 这个接口大致原理是在过滤器中通过 token 获取用户信息然后创建 session, 后续的流程就是 Controller -> Service -> Dao 了.

这次开发没有像之前那样愣头愣脑的, 我想了一下, 对方调用的时候是没有 session id 的, 也就是每次认证之后都会创建一个 session. 那这就可能存在一个大问题了, 假设调用次数非常多的话, 会创建茫茫多的 session, 可能会击垮系统.

所以我的看下我们系统中是如何使用 session 的.

Spring Session 探索

代码跟踪

第一件做的是就是断点 request 获取 session 的代码, 果然是有说法啊!

request.getSession()

%file

@SuppressWarnings("deprecation")
@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends ExpiringSession> extends OncePerRequestFilter {
    public static final String SESSION_REPOSITORY_ATTR = SessionRepository.class.getName();

    public static final int DEFAULT_ORDER = Integer.MIN_VALUE + 50;

    private final SessionRepository<S> sessionRepository;

    private ServletContext servletContext;

    private MultiHttpSessionStrategy httpSessionStrategy = new CookieHttpSessionStrategy();

%file

public void setAttribute(String name, Object value) {
    // 还挺讲究的, 设置 attr 之前先 checkState
    checkState();
    session.setAttribute(name, value);
}

checkState() 也很简单, 就是校验 invalidated 是否为 true.

private void checkState() {
    if(invalidated) {
        throw new IllegalStateException("The HttpSession has already be invalidated.");
    }
}

光从代码找还是有点难找的, 不过还是找到了:

%file

%file

%file

验证 redis 中的数据

上个 debug 的 session 的 key 是: spring:session:sessions:62359810-d2cb-4378-a619-e2c31bb8242c, 看上去是存了一个 hash 结构.

redis 中获取 hash 的命令是:

HGETALL hkey

执行一下:

127.0.0.1:6379> HGETALL spring:session:sessions:62359810-d2cb-4378-a619-e2c31bb8242c
1) "maxInactiveInterval"
2) "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x01Q\x80"
3) "lastAccessedTime"
4) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x82\x00:\x99\x0e"
5) "creationTime"
6) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x82\x00:\x99\x0e"

里面好像还是没有业务数据, 艹, 发现我傻逼了, 断点还没放开,redis 压根还没存这个业务数据.

再执行一遍:

127.0.0.1:6379> HGETALL spring:session:sessions:62359810-d2cb-4378-a619-e2c31bb8242c
 1) "creationTime"
 2) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x82\x00:\x99\x0e"
 3) "lastAccessedTime"
 4) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01\x82\x00D\x1f["
 5) "sessionAttr:HumanSession"
 6) "\xac\xed\x00\x05sr\x00#cn.com.xxx.base.bean.HumanSession\x8f\xbb\xb6d\xf6\x04\x8a\xd5\x02\x00\x1fD\x00\x0bcoordinateXD\x00\x0bcoordinateYZ\x00\tvalidFlagL\x00\x0fautoReceiveFlagt\x00\x13Ljava/lang/Integer;L\x00\x0ebrowserVersiont\x00\x12Ljava/lang/String;L\x00\ndataUnitIDq\x00~\x00\x01L\x00\afromCasq\x00~\x00\x01L\x00\tfromTokenq\x00~\x00\x01L\x00\thumanCodeq\x00~\x00\x02L\x00\ahumanIDq\x00~\x00\x01L\x00\thumanNameq\x00~\x00\x02L\x00\nhumanStyleq\x00~\x00\x02L\x00\ninvalidMsgq\x00~\x00\x02L\x00\x02ipq\x00~\x00\x02L\x00\rleaderLevelIDq\x00~\x00\x01L\x00\x05logIDq\x00~\x00\x01L\x00\tosVersionq\x00~\x00\x02L\x00\npatrolFlagq\x00~\x00\x01L\x00\bportraitq\x00~\x00\x02L\x00\bproxyUrlq\x00~\x00\x02L\x00\nregionCodeq\x00~\x00\x02L\x00\bregionIDq\x00~\x00\x01L\x00\nregionNameq\x00~\x00\x02L\x00\nregionTypeq\x00~\x00\x01L\x00\bserverIpq\x00~\x00\x02L\x00\x06targetq\x00~\x00\x02L\x00\ttelMobileq\x00~\x00\x02L\x00\aunionIDq\x00~\x00\x01L\x00\x06unitIDq\x00~\x00\x01L\x00\bunitNameq\x00~\x00\x02L\x00\buserNameq\x00~\x00\x02xp\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01pppsr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x00\x00q\x00~\x00\x06t\x00\rwizdom:100433sq\x00~\x00\x04\x00\x01\x88Qt\x00\x05xxxxx\x00\bdarkbluept\x00\x0f192.168.213.161pppq\x00~\x00\x06t\x00\x00ppq\x00~\x00\x06psq\x00~\x00\x04\x00\x00\x00\x01t\x00\t127.0.0.1pt\x00\x0b17700000000sq\x00~\x00\x04\x00\x00\x02\xbesq\x00~\x00\x04\x00\x00\x00\x02t\x00\x0f\xe5\xb8\x82\xe7\x9b\x91\xe7\x9d\xa3\xe4\xb8\xad\xe5\xbf\x83t\x00\x05xxx"
 7) "maxInactiveInterval"
 8) "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x01Q\x80"
 9) "sessionAttr:UnionAuthToken"
10) "\xac\xed\x00\x05t\x00$125ba47c-fe01-42b6-b5a2-8a87eb266ddc"

这回有了, 而且还很多.

过期时间如何设置呢?

其实在可以加一个过滤器, 像平常那样设置 session 的过期时间就行:

req.getSession().setMaxInactiveInterval(expireTime);
filterChain.doFilter(servletRequest, servletResponse);

session 存储的小结

Spring Session 对 JavaWeb 中的 session 进行了一层包装, 写业务时候的接口都保持不变, 但是底层的存储从 Tomcat 中的内存变成了 Redis, 而且用户还没有感知.

如果可以能用哨兵模式保证 Redis 的高可以, 感觉是不是就解决了分布式 Session 的问题.

Session 的 invalidate 实现

HttpSession session = request.getSession();
if(session != null){
    session.removeAttribute(/* here is your attr name*/);
    session.invalidate();
}

看来这里主要就是 session.invalidate() 了.

// org.springframework.session.web.http.SessionRepositoryFilter.SessionRepositoryRequestWrapper.
// HttpSessionWrapper#invalidate
public void invalidate() {
    checkState();
    this.invalidated = true;
    requestedSessionInvalidated = true;
    setCurrentSession(null);
    sessionRepository.delete(getId());
}

根据 id 删除 session:

// org.springframework.session.data.redis.RedisOperationsSessionRepository#delete
public void delete(String sessionId) {
    ExpiringSession session = getSession(sessionId, true);
    if(session == null) {
        return;
    }

    String key = getKey(sessionId);
    expirationPolicy.onDelete(session);

    // always delete they key since session may be null if just expired
    this.sessionRedisOperations.delete(key);
}

onDelete 中也删除数据的:

    public void onDelete(ExpiringSession session) {
        long toExpire = roundUpToNextMinute(expiresInMillis(session));
        String expireKey = getExpirationKey(toExpire);
        expirationRedisOperations.boundSetOps(expireKey).remove(session.getId());
    }

盗来的一张图

%file

回到我的问题

第三方通过 token 调用接口创建会话的问题很简单, 调用完了之后就可以将 session invalidate 了.

哈哈


原文地址: Spring Session 的原理

欢迎访问我的博客: https://blog.duhbb.com

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
1月前
|
XML Java 开发者
Spring Boot开箱即用可插拔实现过程演练与原理剖析
【11月更文挑战第20天】Spring Boot是一个基于Spring框架的项目,其设计目的是简化Spring应用的初始搭建以及开发过程。Spring Boot通过提供约定优于配置的理念,减少了大量的XML配置和手动设置,使得开发者能够更专注于业务逻辑的实现。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,为开发者提供一个全面的理解。
36 0
|
10天前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
55 14
|
4月前
|
安全 Java 数据库
一天十道Java面试题----第四天(线程池复用的原理------>spring事务的实现方式原理以及隔离级别)
这篇文章是关于Java面试题的笔记,涵盖了线程池复用原理、Spring框架基础、AOP和IOC概念、Bean生命周期和作用域、单例Bean的线程安全性、Spring中使用的设计模式、以及Spring事务的实现方式和隔离级别等知识点。
|
4月前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
1月前
|
Java 开发者 Spring
Spring AOP 底层原理技术分享
Spring AOP(面向切面编程)是Spring框架中一个强大的功能,它允许开发者在不修改业务逻辑代码的情况下,增加额外的功能,如日志记录、事务管理等。本文将深入探讨Spring AOP的底层原理,包括其核心概念、实现方式以及如何与Spring框架协同工作。
|
5月前
|
Java 应用服务中间件 开发者
Java面试题:解释Spring Boot的优势及其自动配置原理
Java面试题:解释Spring Boot的优势及其自动配置原理
130 0
|
5月前
|
设计模式 监控 Java
解析Spring Cloud中的断路器模式原理
解析Spring Cloud中的断路器模式原理
|
2月前
|
Java Spring 容器
Spring底层原理大致脉络
Spring底层原理大致脉络
|
2月前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
149 9
|
3月前
|
存储 NoSQL Java
Spring Session框架
Spring Session 是一个用于在分布式环境中管理会话的框架,旨在解决传统基于 Servlet 容器的会话管理在集群和云环境中的局限性。它通过将用户会话数据存储在外部介质(如数据库或 Redis)中,实现了会话数据的跨服务器共享,提高了应用的可扩展性和性能。Spring Session 提供了无缝集成 Spring 框架的 API,支持会话过期策略、并发控制等功能,使开发者能够轻松实现高可用的会话管理。
Spring Session框架