問題描述:
##### 在做netty和shiro整合測試時,程序啓動並正常運行一段時間之後會發現shiro出現異常,異常信息爲There is no session with id [xxxxxx]!重啓之後可以恢復但是運行一會兒又會出現該情況,由於我這裏沒有使用shiro的web認證機制,網上一些解決類似此情況的方法無效。以下爲我自己排查分析並解決的過程
第一步GC優化
dubugg模式下發現報錯的地方爲如下所示:
##### 從dubug模式發現,context此時的實例是DefaultSubjectContext實例,而createSubject方法中設置的三個屬性值被封裝到DefaultSubjectContext類的backingMap屬性中,此時都還正常
private final Map<String, Object> backingMap;
public void setAuthenticated(boolean authc) {
put(AUTHENTICATED, authc);
}
//put方法內容
public Object put(String s, Object o) {
return backingMap.put(s, o);
}
##### 繼續向下執行突然發現SubjectContext的值被清空了!也就是HashMap沒了!由此最先懷疑的是shiro是否將hashMap公用了,然後被其他某個線程給幹掉了,查看creatSubjectContext源碼發現每次都會重新實例化一個SubjectContext類,因此我懷疑是給GC幹掉了
protected SubjectContext createSubjectContext() {
return new DefaultSubjectContext();
}
##### 使用java自帶的jvm分析程序,檢查GC執行情況,截圖如下,由於我沒有進行jvm調優,導致GC執行頻率非常高,每條能有好幾次回收,因此可以確定是GC導致HashMap中的數據被回收,從而導致shiro認證失敗,則優化GC即可
優化之後,HashMap中的值不再被GC回收,但是問題依然存在
第二步,根結所在
分析自己得代碼加上網上查到得資料,原因應該是併發訪問shiro導致shiro得session衝突導致,查看SecurityUtils.getSubject();源碼發現shiro是從ThreadLocal中先獲取Subject,如果Subject不存在則重新創建,這裏有個問題,ThreadLocal在netty併發登錄後會導致多個線程獲取到同一個Subject實例,從而得到得sessionkey會與實際得sessionkey不一致從而拋出“There is no session with id”異常
//代碼中獲取Subject得方法
Subject sub = SecurityUtils.getSubject();
AuthenticationToken token = createJwtToken( ctx, msg);
sub.login(token);
// SecurityUtils.getSubject();對應源碼
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Subject.Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
解決思路:
1. 在執行getSubject()方法之前,先執行ThreadContext.remove();方法,將當前shiro線程得TreadLocal緩存中緩存得Subjec實例刪除,則每次執行login之前都重新創建Subject實例,則問題解決
2. 參考互聯網大佬得解決方案:
將Subject sub = SecurityUtils.getSubject();獲取Subject得方式改成“Subject sub = SecurityUtils.getSecurityManager().createSubject(new DefaultSubjectContext());”
到此,問題被徹底解決!