1. 程式人生 > >使用SpringSession管理分散式會話時遇到的反序列化問題

使用SpringSession管理分散式會話時遇到的反序列化問題

問題浮現

我們在使用SpringSession時(其實在問題出現時,我們並沒有意識到和這兒有關聯),遇到了一個隱藏較深的問題。我們像往常一樣,在使用者登入成功之後,將使用者的實體類資訊例項化儲存到了session中,且session最後儲存到了redis裡面,這個過程其實是沒有什麼問題的。

隨著時間的推移業務的迭代變化,使用者實體需要為更新的需求增加額外的欄位,於是我們順其自然的在類中追加其他String型別的屬性,結果上了測試環境,發現我們無法進入到登入頁面,從瀏覽器控制檯上我們發現這個請求因為無法獲取正確的鑑權而不斷報302,並且瀏覽器錯誤主頁提示我們“清除瀏覽器cookie”,當我們照做之後,登入頁面神奇般的可以開啟並且我們能夠順利登入系統和使用系統了。在我們回到服務端檢視後臺日誌時,我們看到時在redis中出現了反序列化的問題,而且日誌報的很蹊蹺,並不是我們的業務程式碼的某一行直接丟擲的日誌,這給我們定位問題帶了一點麻煩。

分析問題

首先反序列化出現問題,我們首先能夠想到的就是給使用者實體加了額外屬性導致的,但是為何會出現反序列化問題呢?原因看起來很簡單,我們的使用者實體類確實實現了Serializable介面,但是我們並沒有顯示的寫出序列化的serialVersionUID,這在實體變更了屬性欄位之後反序列化回來,就一定會反序列化失敗的,因為在你沒有指定序列號serialVersionUID號的時候,系統預設都是以類名稱類屬性等來自動生成的,不同的屬性名稱得出的序列化值肯定是不一樣的,解決的方法很簡單,就是在初始化類的時候,主動寫名序列化serialVersionUID號。

但是我們的程式現在已經在生產環境了,無法做到這一步,因為按照新的類來生成序列號,顯然是前後不一致的,給新加的欄位加上transient欄位?這種做法也步行,這意味著這個欄位的值將無法序列化,後面其他系統肯定是使用不了的,這些額外增加的欄位就沒有存在意義了。

在對報錯日誌進行抽絲剝繭逐行分析之後,發現這個異常居然和SpringSession有關係。

因為我們在使用者成功登入系統之後,使用了request.getSession().setAttribute("xx", "xx")操作,我們將我們的使用者實體放到了session中,並由SpringSession成功的放到了Redis裡面,並且我們的會話時長是48小時;在我們新的實體更新進來之後,系統一發布,開啟登入頁面系統對SpringSession中的內容進行初始化,就發現了使用者實體無法從Redis中初始化回來,因為前後的序列化號對不上了,於是就瘋狂的報反序列化失敗,這是根本問題所在。

解決問題

那怎麼解決呢?很明顯,聰明的瀏覽器給了我們第一個解決方案,那就是清除瀏覽器Session。

因為上面我們說到,SpringSession會在瀏覽器裡面自動設定一個名為SESSION的cookie值,這個值是為了後臺服務能夠在Redis中找到之前存放的Session以便能夠識別是統一使用者,當瀏覽器清除了Cookie之後,這個SESSION的值就失去了,於是系統重新為這個瀏覽器發起請求的使用者重新生成SESSION值,這個請求也就無法在redis中找到對應的Session會話,也就沒有將使用者實體反序列化這個過程,登入程式當然能夠正常進行,而且往後的互動都是正常的了。

那麼還有另外的解決方法,從Redis端,既然SpringSession會用這個SESSION去Redis中找對應的值,那麼我們就將這個Redis中存放的Session內容進行清除,找不到內容自然就無需反序列化了,不過此時正常訪問的使用者,也會因為請求沒有找到統一的Session而認為是會話過期強制退出。這裡在redis中清除內容的方式,可以使用下面的命令:

$ redis-cli keys '*' | xargs redis-cli del
$ redis-cli del spring:session:sessions:7e8383a4-082c-4ffe-a4bc-c40fd3363c5e

第三種方式呢,就是從程式碼端改造,反序列化失敗的本質就是原來的類新增了屬性,而且類沒有顯示的指定序列化serialVersionUID,那麼我們變通一下,保持原來的類不變唄,新增的屬性放到一個繼承了這個使用者實體類的類當中,後面我們就用這個新的類,保持老的類不變(屬性型別和數量等的都不變),那就行了。是的,就是使用新類的方式,新類繼承使用者實體類,保持使用者實體類不變,新類當中增加需要補充的屬性欄位,同時需要序列化,這樣SpringSession在初始化時,它依然能夠反序列找到原來沒有變通的那個類,我們自己也能夠很好的實現我們新的需求了。

問題總結

通過上面的分析我們可以發現,其實SpringSession是替換了HttpSession並在序列化之後存入了Redis裡面的,隱藏較深的持久化對外部沒有寫明serialVersionUID的物件進行的變化較為敏感,如果一旦遺漏了這個細節,在日後變更屬性之後,可能就會暴露出這個問題,而且因為牽扯到使用者登入鑑權,這種影響算是較為惡劣,所以平時應該留意這種小錯誤,儘可能在物件實現序列化之後寫名serialVersionUID,相信這是一種好習慣,當然,可以設定IDEA讓它自動檢測,如果你實現了Serializable介面但是沒有生成serialVersionUID就報錯提醒。希望這次的問題分析能夠對日後使用SpringSession有所幫助,能夠想周到一點,想深入一點。

歡迎工作一到五年的Java工程師朋友們加入Java程式設計師開發: 721575865

群內提供免費的Java架構學習資料(裡面有高可用、高併發、高效能及分散式、Jvm效能調優、Spring原始碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的