原文地址: 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