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






