• Spring Session 的原理


    原文地址: Spring Session 的原理

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


    引言

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

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

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

    Spring Session 探索

    代码跟踪

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

    request.getSession()
    
    • 1

    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();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    file

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

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

    private void checkState() {
    	if(invalidated) {
    		throw new IllegalStateException("The HttpSession has already be invalidated.");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

    file

    file

    file

    验证 redis 中的数据

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

    redis 中获取 hash 的命令是:

    HGETALL hkey
    
    • 1

    执行一下:

    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"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    里面好像还是没有业务数据, 艹, 发现我傻逼了, 断点还没放开,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"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这回有了, 而且还很多.

    过期时间如何设置呢?

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

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

    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    看来这里主要就是 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());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    根据 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    onDelete 中也删除数据的:

    	public void onDelete(ExpiringSession session) {
    		long toExpire = roundUpToNextMinute(expiresInMillis(session));
    		String expireKey = getExpirationKey(toExpire);
    		expirationRedisOperations.boundSetOps(expireKey).remove(session.getId());
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5

    盗来的一张图

    file

    回到我的问题

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

    哈哈


    原文地址: Spring Session 的原理

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

  • 相关阅读:
    机器学习_PySpark-3.0.3字符标签索引化(StringIndex)实例
    基于 ceph-deploy 部署 Ceph 集群
    leetcode 50. Pow(x, n) (快速幂)
    中间件 | Redis - [包 & 工具]
    【计算机组成 课程笔记】7.1 存储层次结构概况
    Leetcode1071. 字符串的最大公因子(三种方法,带详细解析)
    SpringBoot框架集成Dubbo
    avue中 curd的列表配置
    Android 性能优化之黑科技开道(二)
    qt 绘图
  • 原文地址:https://blog.csdn.net/ea8d1n3/article/details/125884822