JAVA 分布式系統如何實現session共享?
當然業界已經有很多成熟的解決方案,我羅列如下:
1.服務器實現的session復制或session共享,這類型的共享session是和服務器緊密相關的,比如webSphere或JBOSS在搭建集群時候可以配置實現session復制或session共享,但是這種方式有一個致命的缺點,就是不好擴展和移植,比如我們更換服務器,那么就要修改服務器配置。
2018java零基礎視頻教程從入門到精通項目實戰JavaWeb/JavaSE¥8.8
購買
2.利用成熟的技術做session復制,比如12306使用的gemfire,比如常見的內存數據庫如redis或memorycache,這類方案雖然比較普適,但是嚴重依賴于第三方,這樣當第三方服務器出現問題的時候,那么將是應用的災難。
3.將session維護在客戶端,很容易想到就是利用cookie,但是客戶端存在風險,數據不安全,而且可以存放的數據量比較小,所以將session維護在客戶端還要對session中的信息加密。
我們實現的方案可以說是第二種方案和第三種方案的合體,可以利用gemfire實現session復制共享,還可以將session維護在redis中實現session共享,同時可以將session維護在客戶端的cookie中,但是前提是數據要加密。這三種方式可以迅速切換,而不影響應用正常執行。我們在實踐中,首選gemfire或者redis作為session共享的載體,一旦session不穩定出現問題的時候,可以緊急切換cookie維護session作為備用,不影響應用提供服務,下面我簡單介紹方案中session共享的實現方式和原理。
這里主要講解redis和cookie方案,gemfire比較復雜大家可以自行查看gemfire工作原理。利用redis做session共享,首先需要與業務邏輯代碼解耦,不然session共享將沒有意義,其次支持動態切換到客戶端cookie模式。redis的方案是,重寫服務器中的HttpSession和HttpServletRequest,首先實現HttpSession接口,重寫session的所有方法,將session以hash值的方式存在redis中,一個session的key就是sessionID,setAtrribute重寫之后就是更新redis中的數據,getAttribute重寫之后就是獲取redis中的數據,等等需要將HttpSession的接口一一實現。
實現了HttpSesson,那么我們先將該session類叫做MySession(當然實踐中不是這么命名的),當MySession出現之后問題才開始,怎么能在不影響業務邏輯代碼的情況下,還能讓原本的request.getSession()獲取到的是MySession,而不是服務器原生的session。這里,我決定重寫服務器的HttpServletRequet,這里先稱為MyRequest,但是這可不是單純的重寫,我需要在原生的request基礎上重寫,于是我決定在filter中,實現request的偷梁換柱,我的思路是這樣的,MyRequest的構建器,必須以request作為參數,于是我在filter中將服務器原生的request(也有可能是框架封裝過的request),當做參數new出來一個MyRequest,并且MyRequest也實現了HttpServletRequest接口,其實就是對原生request的一個增強,這里主要重寫了幾個request的方法,但是最重要的是重寫了request.getSession(),寫到這里大家應該都明白為什么重寫這個方法了吧,當然是為了獲取MySession,于是這樣就在filter中,偷偷的將原生的request換成MyRequest了,然后再將替換過的request傳入chan.doFilter(),這樣filter時候的代碼都使用的是MyRequest了,同時對業務代碼是透明的,業務代碼獲取session的方法仍然是request.getSession(),但其實獲取到的已經是MySession了,這樣對session的操作已經變成了對redis的操作。這樣實現的好處有兩個,第一開發人員不需要對session共享做任何關注,session共享對用戶是透明的;第二,filter是可配置的,通過filter的方式可以將session共享做成一項可插拔的功能,沒有任何侵入性。
這個時候已經實現了一套可插拔的session共享的框架了,但是我們想到如果redis服務出了問題,這時我們該怎么辦呢,于是我們延續redis的想法,想到可以將session維護在客戶端內(加密的cookie),當然實現方法還是一樣的,我們重寫HttpSession接口,實現其所有方法,比如setAttribute就是寫入cookie,getAttribute就是讀取cookie,我們可以將重寫的session稱作MySession2,這時怎么讓開發人員透明的獲取到MySession2呢,實現方法還是在filter內偷梁換柱,在MyRequest加一個判斷,讀取sessionType配置,如果sessionType是redis的,那么getSession的時候獲取到的是MySession,如果sessionType是coolie的,那么getSession的時候獲取到的是MySession2,以此類推,用同樣的方法就可以獲取到MySession 3,4,5,6等等。
這樣兩種方式都有了,那么我們怎實現兩種session共享方式的快速切換呢,剛剛我提到一個sessionType,這是用來決定獲取到session的類型的,只要變換sessionType就能實現兩種session共享方式的切換,但是sessionType必須對所有的服務器都是一致的,如果不一致那將會出現比較嚴重的問題,我們目前是將sessionType維護在環境變量里,如果要切換sessionType就要重啟每一臺服務器,完成session共享的轉換,但是當服務器太多的時候將是一種災難。而且重啟服務意味著服務的中斷,所以這樣的方式只適合服務器規模比較小,而且用戶量比較少的情況,當服務器太多的時候,務必需要一種協調技術,能夠讓服務器能夠及時獲取切換的通知。基于這樣的原因,我們選用zookeeper作為配置平臺,每一臺服務器都會訂閱zookeeper上的配置,當我們切換sessionType之后,所有服務器都會訂閱到修改之后的配置,那么切換就會立即生效,當然可能會有短暫的時間延遲,但這是可以接受的。
方案大體分享完了,在此將我的收獲記錄下來,我們已經實現了一個版本,大家也可以發揮想象,分享不同的方案,我們一起進步,文中有不足之處,還望各位不吝賜教。
本文轉載于博客園,作者:左側碼工
原文:https://www.cnblogs.com/zuolun2017/p/8516764.html