JAVA 分布式系統(tǒng)如何實(shí)現(xiàn)session共享?

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