原文地址: 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 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 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) "xacxedx00x05srx00x11java.lang.Integerx12xe2xa0xa4xf7x81x878x02x00x01Ix00x05valuexrx00x10java.lang.Numberx86xacx95x1dx0bx94xe0x8bx02x00x00xpx00x01Qx80" 3) "lastAccessedTime" 4) "xacxedx00x05srx00x0ejava.lang.Long;x8bxe4x90xccx8f#xdfx02x00x01Jx00x05valuexrx00x10java.lang.Numberx86xacx95x1dx0bx94xe0x8bx02x00x00xpx00x00x01x82x00:x99x0e" 5) "creationTime" 6) "xacxedx00x05srx00x0ejava.lang.Long;x8bxe4x90xccx8f#xdfx02x00x01Jx00x05valuexrx00x10java.lang.Numberx86xacx95x1dx0bx94xe0x8bx02x00x00xpx00x00x01x82x00:x99x0e"
里面好像还是没有业务数据, 艹, 发现我傻逼了, 断点还没放开,redis 压根还没存这个业务数据.
再执行一遍:
127.0.0.1:6379> HGETALL spring:session:sessions:62359810-d2cb-4378-a619-e2c31bb8242c 1) "creationTime" 2) "xacxedx00x05srx00x0ejava.lang.Long;x8bxe4x90xccx8f#xdfx02x00x01Jx00x05valuexrx00x10java.lang.Numberx86xacx95x1dx0bx94xe0x8bx02x00x00xpx00x00x01x82x00:x99x0e" 3) "lastAccessedTime" 4) "xacxedx00x05srx00x0ejava.lang.Long;x8bxe4x90xccx8f#xdfx02x00x01Jx00x05valuexrx00x10java.lang.Numberx86xacx95x1dx0bx94xe0x8bx02x00x00xpx00x00x01x82x00Dx1f[" 5) "sessionAttr:HumanSession" 6) "xacxedx00x05srx00#cn.com.xxx.base.bean.HumanSessionx8fxbbxb6dxf6x04x8axd5x02x00x1fDx00x0bcoordinateXDx00x0bcoordinateYZx00tvalidFlagLx00x0fautoReceiveFlagtx00x13Ljava/lang/Integer;Lx00x0ebrowserVersiontx00x12Ljava/lang/String;Lx00ndataUnitIDqx00~x00x01Lx00afromCasqx00~x00x01Lx00tfromTokenqx00~x00x01Lx00thumanCodeqx00~x00x02Lx00ahumanIDqx00~x00x01Lx00thumanNameqx00~x00x02Lx00nhumanStyleqx00~x00x02Lx00ninvalidMsgqx00~x00x02Lx00x02ipqx00~x00x02Lx00rleaderLevelIDqx00~x00x01Lx00x05logIDqx00~x00x01Lx00tosVersionqx00~x00x02Lx00npatrolFlagqx00~x00x01Lx00bportraitqx00~x00x02Lx00bproxyUrlqx00~x00x02Lx00nregionCodeqx00~x00x02Lx00bregionIDqx00~x00x01Lx00nregionNameqx00~x00x02Lx00nregionTypeqx00~x00x01Lx00bserverIpqx00~x00x02Lx00x06targetqx00~x00x02Lx00ttelMobileqx00~x00x02Lx00aunionIDqx00~x00x01Lx00x06unitIDqx00~x00x01Lx00bunitNameqx00~x00x02Lx00buserNameqx00~x00x02xpx00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x01pppsrx00x11java.lang.Integerx12xe2xa0xa4xf7x81x878x02x00x01Ix00x05valuexrx00x10java.lang.Numberx86xacx95x1dx0bx94xe0x8bx02x00x00xpx00x00x00x00qx00~x00x06tx00rwizdom:100433sqx00~x00x04x00x01x88Qtx00x05xxxxxx00bdarkblueptx00x0f192.168.213.161pppqx00~x00x06tx00x00ppqx00~x00x06psqx00~x00x04x00x00x00x01tx00t127.0.0.1ptx00x0b17700000000sqx00~x00x04x00x00x02xbesqx00~x00x04x00x00x00x02tx00x0fxe5xb8x82xe7x9bx91xe7x9dxa3xe4xb8xadxe5xbfx83tx00x05xxx" 7) "maxInactiveInterval" 8) "xacxedx00x05srx00x11java.lang.Integerx12xe2xa0xa4xf7x81x878x02x00x01Ix00x05valuexrx00x10java.lang.Numberx86xacx95x1dx0bx94xe0x8bx02x00x00xpx00x01Qx80" 9) "sessionAttr:UnionAuthToken" 10) "xacxedx00x05tx00$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();
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



