Session 共享有多種解決方法,經常使用的有四種:客戶端 Cookie 保存、服務器間 Session 同步、使用集羣管理 Session(如本文要介紹的Memcached Session Manager) 、把 Session 持久化到數據庫:css
客戶端 Cookie 保存 以cookie加密的方式保存在客戶端.優勢是減輕服務器端的壓力,每次session信息被寫在客服端,而後經瀏覽器再次提交到服務器。即便兩次請求在集羣中的兩臺服務器上完成,也能夠到達session共享。java
將 session 持久化到數據中 這種共享session的方式即將session信息存入數據庫中,其它應用能夠從數據庫中查出 session 信息。目前採用這種方案時所使用的數據庫通常爲mysql。 利用數據庫共享 session 的方案有必定的實用性,但也有以下缺點:首先 session 的併發讀寫在數據庫中完成,對 mysql 的性能要求比較高;其次,咱們須要額外地實現 session 淘汰(超時)邏輯代碼,即定時從數據庫表中更新和刪除 session 信息,增長了工做量。node
使用服務器間 session 同步 使用主-從服務器的架構,當用戶在主服務器上登陸後,經過腳本或者守護進程的方式,將 session 信息傳遞到各個從服務器中,這樣用戶訪問其它的從服務器時,就能夠讀到session信息。 缺點:好比速度慢、不穩定等,另外,若是 session 信息傳遞是主->從單向的,會有一些風險,好比主服務器down了,其它服務器沒法得到 session 信息mysql
使用集羣統一管理Session 提供一個集羣保存 session 共享信息.其餘應用通通把本身的 session 信息存放到 session 集羣服務器組。當應用系統須要 session 信息的時候直接到 session 集羣服務器上讀取。目前大多都是使用 Memcache 來對 Session 進行存儲。 以 Memcache 來實現 Session 共享的方式目前比較流行的有兩種實現方案,下面主要對這兩種方案進行介紹。linux
使用Filter方式: 此方式使用過濾器的方式從新對httpRequest 對象進行了包裝,並加入memcached客戶端,此方式的優勢是:使用簡單,把過濾器配置進去便可,另外比較靈活,由於它是在客戶端實現的,配置比較靈活,並且服務器無關,你能夠在任何支持servlet的容器上部署。nginx
使用memcached-session-manager方式: memcached-session-manager,俗稱 MSM ,是一個用於解決分佈式 tomcat 環境下 session 共享的問題的開源解決方案。它的實現原理爲以tomcat插件的方式部署在服務器,修改了 servlet 容器代碼中的 session 相關代碼,使其鏈接 memcached ,在 memcached 中建立和更新session。MSM擁有以下特性: 支持Tomcat六、Tomcat七、Tomcat8 支持黏性、非黏性 Session 無單一故障點 可處理 tomcat 故障轉移 可處理 memcached 故障轉移 插件式 session 序列化 容許異步保存 session ,以提高響應速度 只有當 session 有修改時,纔會將 session 寫回 memcached JMX 管理&監控 Memcached-session-manager 支持tomcat六、tomcat七、tomcat8 ,利用 Value(Tomcat 閥)對 Request 進行跟蹤。 Request 請求到來時,從 memcached 加載 session , Request 請求結束時,將 tomcat session 更新至 memcached ,以達到 session 共享之目的, 支持 sticky 和 non-sticky 模式。git
優勢:開發者不用考慮session共享的問題了,能夠專一於業務邏輯開發,像正常使用 session 那樣使用就完事了。不用顯示編寫代碼,只須要對服務器進行配置便可使用。github
缺點:若是你想改變session策略的話,必須從新部署每一個服務器的servlet容器。web
咱們都知道對於一些比較大型的網站,在正式部署時通常是部署在不一樣故障域的多臺應用服務器上,以 JavaEE 應用爲例,通常咱們都會部署在 tomcat 下,假如咱們部署了10臺 tomcat 服務器,那這10臺 tomcat 多是部署在不一樣的機器上,而後將應用程序copy到這10臺 tomcat 下,而後啓動全部 tomcat ,通常來講這樣作的目的是爲了達到負載均衡以及避免單點故障,另外也考慮到國內網絡環境的緣由,避免跨網絡運營商訪問而致使訪問速度低下的問題,_固然不要忘了坐鎮這10臺 tomcat 前面的還有咱們的反向代理服務器,好比 nginx_ ,我今天主要講的是,對於這種分佈式 tomcat 環境,咱們如何保證 session 的惟一性(也能夠說是 session 的共享)。這也是在目前的不少項目中須要解決的一個問題,固然實際上這並非什麼新的議題,以前就有不少解決方案,可是通常來講的大致的解決方案是本身經過編寫一段代碼或者經過配置 tomcat 的 filter ,將產生的 session 放到同一個內存數據庫中,事實上這確實可行的,只不過我比較懶,我老是以爲這種問題應該有更省事更成熟的解決方案,那確實是有的,也就是我立刻介紹的 Memcached Session Manager,簡稱 msm ,這就是一個用於解決分佈式 tomcat 環境下 session 共享的問題的開源解決方案。
想象下web應用程序運行在多個tomcat,指望session能實現故障轉移。你須要一個可擴展的方案-僅僅增長tomcat來處理更多的會話。該方案可經過memcached節點存儲備份會話來實現。當一個tomcat負載太重或掛掉時,其餘tomcat就會接管這個tomcat,從相應的memcached節點中獲取會話數據,以後就能夠服務這個會話。固然多個tomcat前面還須要有一個負載均衡,好比nginx。sql
首先談下tomcat故障轉移
msm安裝在tomcat裏,tomcat會在本地保留全部會話信息就像StandardManager同樣。 此外,一個請求完成後,session會被備份到memcached節點。 當服務同一會話的下一次請求時,tomcat能夠在本地找到這個會話數據,同一會話的第二次請求 處理完後,會話數據會更新到memcached節點。 假設處理某個會話的tomcat掛了。 那麼下次請求會被路由到另外一個tomcat。而這個tomcat沒有在本地保存該會話的數據。所以它 會去相應的memcached(根據請求頭中sessionid的後綴,後面配置$CATALINA_HOME/conf/context.xml時,memcachedNodes="n1:localhost:11211,n2:localhost:11212",就是n1,n2)中查找這次請求的會話數據並保存到本地。 這樣這個tomcat就能夠處理這次會話了。當這個tomcat處理完這次會話,它會將更新相應memcached節點存儲的session信息。
注:上圖8 tomcat1故障,路由到tomcat2由負載均衡完成(如nginx)。
再談下memcahced故障轉移
msm也實現了memcached的故障轉移。當一個memcached節點不可用時,session信息就會被轉移到其餘memcached節點。 與此同時,sessionid會被修改,一個新的JESESSIONID(響應頭會有Set-Cookie:JESSIONID;XXXXXXXXXXXXX)會被髮送 到瀏覽器端。當你使用sticky session時,確保你的負載均衡不會給sessionid添加後綴。
MSM(memcached-session-manager) 支持tomcat6 和tomcat7 ,利用 Value(Tomcat 閥)對Request進行跟蹤。Request請求到來時,從memcached加載session,Request請求結束時,將tomcat session更新至memcached,以達到session共享之目的, 支持 sticky 和 non-sticky 模式。須要注意的是使用sticky模式時須要配置jvmroute參數,配置方式以下:
配置$CATALINA_HOME/conf/server.xml
<Engine name="Catalina"defaultHost="localhost"jvmRoute="tomcatx">
注意每臺tomcat的jvmroute參數都不能同樣
Sticky 模式:tomcat本地session 爲 主session, memcached 爲備 session。Request請求到來時, 從memcached加載備 session 到 tomcat (僅當tomcat jvmroute發生變化時,不然直接取tomcat本地session);Request請求結束時,將tomcat本地session更新至memcached,以達到主備同步之目的。下面是sticky模式時響應的流程圖(圖片來源網絡):
non-sticky模式本文不介紹。詳見:http://gong1208.iteye.com/blog/1596120
http://domain:8001/
,而訪問tomcat2須要用```http://domain:8002/1. nginx處理靜態資源的性能比tomcat好不少 因爲nginx須要安裝sticky session,故須要在linux環境下完成 ### 準備工做 1. 下載tomcat,nginx,memcached,msm(``` http://repo1.maven.org/maven2/de/javakaffee/msm/ ```),須要根據tomcat的版本下載相應版本的msm,因爲用到了memcached,所以還要用到memcached的java api包,``` http://mvnrepository.com/artifact/de.javakaffee.msm/memcached-session-manager ```,我下載的是1.8.3版本。序列化方式使用的是kryo,注意版本要求與msm版本基本一致,建議統一採用最新穩定版,以下。其中序列化方式是可選的,所以還要下載kryo-serializers``` http://mvnrepository.com/artifact/de.javakaffee/kryo-serializers和kryo ``` http://repo1.maven.org/maven2/com/googlecode/kryo/1.04/,還須要下載minlog``` http://mvnrepository.com/artifact/com.googlecode/minlog/1.2
,asm
http://mvnrepository.com/artifact/asm/asm ,reflectasm
http://mvnrepository.com/artifact/com.googlecode/reflectasm/1.01
```。將下載好jar包放到兩個tomcat的$CATALINA_HOME/lib目錄下。
2015/12/07 15:09 15,979 annotations-api.jar 2016/01/11 15:51 43,581 asm-3.3.1.jar 2015/12/07 15:09 55,011 catalina-ant.jar 2015/12/07 15:09 131,075 catalina-ha.jar 2015/12/07 15:09 260,900 catalina-tribes.jar 2015/12/07 15:09 1,651,858 catalina.jar 2015/12/07 15:09 2,310,271 ecj-4.4.2.jar 2015/12/07 15:09 55,505 el-api.jar 2015/12/07 15:09 124,695 jasper-el.jar 2015/12/07 15:09 601,087 jasper.jar 2015/12/07 15:09 87,805 jsp-api.jar 2016/01/12 09:38 94,830 kryo-1.04.jar 2016/01/12 09:30 62,112 kryo-serializers-0.11.jar 2016/01/11 16:50 147,025 memcached-session-manager-1.8.3.jar 2016/01/11 15:44 11,284 memcached-session-manager-tc7-1.8.3.jar 2016/01/12 09:39 4,879 minlog-1.2.jar 2016/01/11 15:42 29,328 msm-kryo-serializer-1.8.3.jar 2016/01/12 09:42 11,615 reflectasm-1.01.jar 2015/12/07 15:09 198,017 servlet-api.jar 2016/01/11 16:31 467,218 spymemcached-2.11.7.jar 2015/12/07 15:09 6,522 tomcat-api.jar 2015/12/07 15:09 790,612 tomcat-coyote.jar 2015/12/07 15:09 234,043 tomcat-dbcp.jar 2015/12/07 15:09 71,860 tomcat-i18n-es.jar 2015/12/07 15:09 43,793 tomcat-i18n-fr.jar 2015/12/07 15:09 47,036 tomcat-i18n-ja.jar 2015/12/07 15:09 127,483 tomcat-jdbc.jar 2015/12/07 15:09 32,893 tomcat-util.jar 2015/12/07 15:09 214,782 tomcat7-websocket.jar 2015/12/07 15:09 36,271 websocket-api.jar
### 配置tomcat 修改兩個tomcat的$CATALINA_HOME/conf/server.xml 1. tomcat1
<Service name="Catalina"> <Connector port="8001" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> <Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat1"> <Realm className="org.apache.catalina.realm.LockOutRealm"> <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/> </Realm> <Host name="localhost" unpackWARs="true" autoDeploy="true"> <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log." suffix=".txt" pattern="%h %l %u %t "%r" %s %b" /> <Context docBase="E:/HPCWorkFolder/ServerCluster/nginx-tomcat-memcached-manager-session/www/webapptomcat1" path="" reloadable="true" /> </Host> </Engine> </Service> ```
tomcat1 Engine標籤的jvmRoute屬性值配置爲tomcat1
<Service name="Catalina"> <Connector port="8002" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> <Connector port="8019" protocol="AJP/1.3" redirectPort="8443" /> <Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat2"> <Realm className="org.apache.catalina.realm.LockOutRealm"> <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/> </Realm> <Host name="localhost" unpackWARs="true" autoDeploy="true"> <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log." suffix=".txt" pattern="%h %l %u %t "%r" %s %b" /> <Context docBase="E:/HPCWorkFolder/ServerCluster/nginx-tomcat-memcached-manager-session/www/webapptomcat2" path="" reloadable="true" /> </Host> </Engine> </Service>
tomcat2 Engine標籤的jvmRoute屬性值配置爲tomcat2
修改$CATALINA_HOME/conf/context.xml
tomcat1:
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager" memcachedNodes="n1:localhost:11211,n2:localhost:11212" failoverNodes="n1" requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$" transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory" />
tomcat2: 另外一個failoverNodes="n2"
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager" memcachedNodes="n1:localhost:11211,n2:localhost:11212" failoverNodes="n2" requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$" transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"/>
意思是tomcat1優先將session存到memcached1,tomcat2優先將session存到memchaced2
memcached -p11211 -m32 memcached -p11212 -m32
開啓兩個tomcat
tomcat1終端顯示:
- finished initialization: - sticky: true - operation timeout: 1000 - node ids: [n2] - failover node ids: [n1] - storage key prefix: null --------
tomcat2終端顯示:
- finished initialization: - sticky: true - operation timeout: 1000 - node ids: [n1] - failover node ids: [n2] - storage key prefix: null -------- --------
表示tomcat1,tomcat2,配置成功了
http://my.oschina.net/u/1167421/blog/604633
在tomcat1的web應用目錄下放一個index.jsp文件,內容以下: tomcat1<br> <%=request.getSession(true).getId()%>
在tomcat2的web應用目錄下放一個index.jsp文件,內容以下: tomcat2<br> <%=request.getSession(true).getId()%>
第1次請求
看到響應頭中SetCookie
JSESSIONID=786579866D7914523416D9C35A3F74DB-n2.tomcat1 以及route(由nginx設置的)
第2次請求
看到響應頭中已經沒有Set-Cookie
第3次請求(在瀏覽器端發送第3次請求前,關掉tomcat1)
JSESSIONID=786579866D7914523416D9C35A3F74DB-n1.tomcat2 以及route,其中route值已經變了跟第1次請求相比
以上結果說明,tomcat1優先將session存儲到n2,tomcat2優先將session存儲到n1,第2次請求,tomcat1掛掉,請求轉交tomcat2處理,tomcat2根據SESSIONID後綴n2去memcached2查找SESSSION信息,修改JESESSIONID的後綴,由786579866D7914523416D9C35A3F74DB-n2.tomcat1變成786579866D7914523416D9C35A3F74DB-n1.tomcat2。前綴仍是786579866D7914523416D9C35A3F74DB。作到了SESSION保持。