MSM--Memcached_Session_Manager介紹及使用
我 們都知道對於一些大型的web2.0的網站,在正式部署時通常是部署在不一樣故障域的多臺應用服務器上,以j2ee應用爲例,通常咱們都會部署在 tomcat下,假如咱們部署了10臺tomcat服務器,那這10臺tomcat多是部署在不一樣的機器上,而後將應用程序copy到這10臺 tomcat下,而後啓動全部tomcat,通常來講這樣作的目的是爲了達到負載均衡以及避免單點故障,另外也考慮到國內網絡環境的緣由,避免跨網絡運營 商訪問而致使訪問速度低下的問題,固然不要忘了坐鎮這10臺tomcat前端的還有咱們的反向代理服務器,好比nginx,這個就是另外一個話題了,我今天 主要講的是,對於這種分佈式tomcat環境,咱們如何保證session 的惟一性(我假定你知道session是什麼)。這也是在日期公司的一個項目中負責解決的一個問題,固然實際上這並非什麼新的議題,以前就有不少解決方 案,可是通常來講的大致的解決方案是本身經過編寫一段代碼或者經過配置tomcat的filter,將產生的session放到同一個內存數據庫中,事實 上這確實可行的,只不過我比較懶,我老是以爲這種問題應該有更省事更成熟的解決方案,那確實是有的,也就是我立刻介紹 的 Memcached_Session_Manager,簡稱msm,這就是一個用於解決分佈式tomcat環境下session共享的問題的開源解決 方案。 css
一 簡介
(如下內容由我的根據msm官網大意翻譯,原文地址:http://code.google.com/p/memcached-session-manager/) html
引言 前端
MSM--memcached session manager是一個高可用的Tomcat session共享解決方案,除了能夠從本機內存快速讀取Session信息(僅針對黏性Session)外,同時可以使用memcached存取Session,以實現高可用。 java
對於非黏性Session,memcached直接存儲session。 nginx
除memcached外,還能夠其餘緩存組件如memcachedb, membase等。 web
特性 數據庫
支持Tomcat6、Tomcat7 json
支持黏性、非黏性Session 瀏覽器
無單一故障點 緩存
可處理tomcat故障轉移
可處理memcached故障轉移
插件式session序列化
容許異步保存session,以提高響應速度
只有當session有修改時,纔會將session寫回memcached
JMX管理&監控
MSM解決的問題
假設你有一個Tomcat集羣,使用黏性session,如何應對單點故障問題?爲了應對更多的併發量和可用性,你能夠不斷的增長Tomcat節點,可是單點故障仍舊會是個問題:若是使用黏性Session,一個Tomcat故障時,其餘Tomcat並不能接管故障Tomcat節點的Session。
解決此問題的思路就是將黏性Session同時保存在Memcached中,若是單個Tomcat發生故障,集羣中的其餘Tomcat能夠從Memcached中獲得Session信息。
【注】對於非黏性Session,MSM V1.4.0及之後版本已經支持。
MSM如何工做
【注】如下論述僅針對黏性Session
安裝在Tomcat上的MSM使用本機內存保存session,和StandardManager同樣。另外,當一個請求結束時,session會被送回Memcached進行備份。當下一次請求開始時,本地Session可用,直接服務,請求結束後,session又被送回Memcached備份。
當集羣中的一個Tomcat掛掉,下一次請求會被路由到其餘Tomcat上。負責處理此此請求的Tomcat並不清楚Session的信息。此時它會從Memcached查找該Session,更新該Session並將其保存在本機內容。這次請求結束,session被修改,送回Memcached備份。
.
What else?
上邊介紹的是處理Tomcat故障轉移,MSM又是如何處理Memcached故障轉移呢?
若是一個Memcached故障,當前Memcached中的Session會轉移到其餘Memcached節點,同時,JSESSIONID被修改並送回瀏覽器。
若是使用黏性Session,應確保loadbalancer中配置生成的JSESSIONID無任何後綴。
SESSIONID的格式
MSM知道Memcached節點列表,這些節點標識會存儲在SESSIONID中,SESSIONID值相似:602F7397FBE4D9932E59A9D0E52FE178-n1 【其中n1爲Memcached節點標識】
二 安裝
參考網站:http://code.google.com/p/memcached-session-manager/wiki/SetupAndConfiguration
環境
1.Linux 環境
2.Tomcat7.X (3臺),在同一臺機器上啓動三臺Tomcat須要修改conf/server.xml中的三個端口:8080,8005,8009
3.MemBase (1臺),也可採用memcached,使用方法同樣,只是在java客戶端鏈接時有不一樣。
4.nginx
準備的jar包
注意:不一樣的tomcat版本(tomcat6,tomcat7)所需的包不同,須要針對tomcat版本下載對應的包.
1.這是採用的最新穩定版1.6.1,序列化方式使用的是kryo,注意版本要求與msm版本基本一致,建議統一採用最新穩定版,以下。其中序列化方式是可選的。
2.這是採用的javolution的序列化方式全部須要的包
建議採用kryo序列化方式,效率更高。
配置
1.將上面所提到的包所有拷貝到tomcat的lib下(三臺tomcat都須要)
2.修改每臺tomcat的conf目錄下得context.xml文件或者server.xml文件,在其中加入以下任意一段代碼(注意:當使用多臺tomcat時,必定要使用non-sticky模式):
A:使用默認的sticky session,kryo序列化方式,memcached緩存
Java代碼
- <Context>
- ...
- <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
- memcachedNodes="n1:host1.yourdomain.com:11211,n2:host2.yourdomain.com:11211"
- failoverNodes="n1"
- requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
- transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
- />
- </Context>
B:使用non-sticky session
Java代碼
- <Context>
-
- ...
-
- <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
- memcachedNodes="n1:host1.yourdomain.com:11211,n2:host2.yourdomain.com:11211"
- sticky="false"
- sessionBackupAsync="false"
- lockingMode="uriPattern:/path1|/path2"
- requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
- transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
- />
-
- </Context>
C:使用membase
Java代碼
- <Context>
-
- ...
-
- <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
- memcachedNodes="http://host1.yourdomain.com:8091/pools"
- username="bucket1"
- password="topsecret"
- memcachedProtocol="binary"
- sticky="false"
- sessionBackupAsync="false"
- requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
- transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
- />
-
- </Context>
當使用javolution序列化方式時將:
Java代碼
- transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory」
替換爲:
Java代碼
- transcoderFactoryClass="de.javakaffee.web.msm.serializer.javolution.JavolutionTranscoderFactory"
配置完成後,分別啓動tomcat,正常啓動說明msm配置成功。
3.最後附上nginx配置:
修改配置文件nginx\conf\nginx.conf
1. 找到內容server {
在它的上面加入以下內容:
Java代碼
- upstream 10.6.53.120 {
- #ip_hash; ----#ip_hash策略將同一IP的全部請求都轉發到同一應用服務器
- server 10.6.53.120:8080;---------個人tomcat端口號
- server 10.6.53.120:7080;
- server 10.6.53.120:6080;
- }<span>(</span><span>這是負載切換使用的服務器網站</span><span>IP)</span>
2. 找到
Java代碼
- location / {
- root html;
- index index.html index.htm;
- }
把內容更改以下:
Java代碼
- location / {
- proxy_pass http://10.6.53.120
- proxy_redirect default;
- proxy_connect_timeout 10; added by me(跟代理服務器鏈接的超時時間,必須留意這個time out時間不能超過10秒.當一臺服務器當掉時,過10秒轉發到另一臺服務器)
- }
3. 找到
Java代碼
- server {
- listen 80;
- server_name localhost;
把內容改爲以下:
Java代碼
- server {
- listen 80;
- server_name 10.6.53.120;
(這是監聽訪問域名綁定那臺服務器80端口的請求)
到這裏全部的配置已經完成,如今準備一個簡單的web工程,並分別部署到三臺tomcat下。啓動memcached(membase),啓動三臺tomcat,啓動nginx,而後在地址欄輸入url地址,看可否成功訪問。關閉其中一臺tomcat,看是否仍然可以正常訪問,可以則說明配置nginx配置成功。
三 原理
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
Java代碼
- <Engine name="Catalina"defaultHost="localhost"jvmRoute="tomcat2">
注意每臺tomcat的jvmroute參數都不能同樣
Sticky 模式:tomcat session 爲 主session, memcached 爲備 session。Request請求到來時, 從memcached加載備 session 到 tomcat (僅當tomcat jvmroute發生變化時,不然直接取tomcat session);Request請求結束時,將tomcat session更新至memcached,以達到主備同步之目的。下面是sticky模式時響應的流程圖(圖片來源網絡):
Non-Sticky模式:tomcat session 爲 中轉session, memcached1 爲主 sessionmemcached 2 爲備session。Request請求到來時,從memcached 2加載備 session 到 tomcat,(當 容器 中仍是沒有session 則從memcached1加載主 session 到 tomcat, 這種狀況是隻有一個memcached節點,或者有memcached1 出錯時),Request請求結束時,將tomcat session更新至 主memcached1和備memcached2,而且清除tomcat session 。以達到主備同步之目的,以下是non-sticky模式的響應流程圖:(圖片來源網絡)。
項目須要統計在線用戶數量,系統部署在集羣環境下,使用會話粘貼的方式解決Session問題。要想獲得真實在線用戶數,必須是全部節點的總和。
這裏考慮使用memcached存放用戶登陸數據,key爲userid統計在線用戶數據,只須要統計key的總數。memcached由於性能的緣故,
沒有提供遍歷整個緩存當中對象的功能,不過memcached也提供了不少命令來監控memcached的狀態,例如stats命令就有:
Java代碼
- stats
- stats reset
- stats malloc
- stats maps
- stats sizes
- stats slabs
- stats items
- stats cachedump slab_id limit_num
- stats detail [on|off|dump]
使用命令【stats items】查詢查詢到全部的slab,再使用命令【cachedump 1 0】命令找出全部的Key信息。但過時的key也會被查詢出來,因此須要對all keys執行一遍查詢,過濾掉過時的key。也能夠經過【cachedump 1 0】命令查詢出來的key過時時間與當前時間進行比較。判斷是否過時。這裏的過時時間爲 session.getMaxInactiveInterval()的值。
Java代碼
- Iterator<Map<String, String>> iterSlabs = client.getStats("items").values().iterator();
- Set<String> set = new HashSet<String>();
- while(iterSlabs.hasNext()) {
- Map<String, String> slab = iterSlabs.next();
- for(String key : slab.keySet()) {
- String index = key.split(":")[1];
- set.add(index);
- }
- }
-
- //統計
- List<String> list = new LinkedList<String>();
- for(String v : set) {
- String commond = "cachedump ".concat(v).concat(" 0");
- Iterator<Map<String, String>> iterItems = client.getStats(commond).values().iterator();
- while(iterItems.hasNext()) {
- Map<String, String> items = iterItems.next();
- list.addAll(items.keySet());
- }
- }
-
- return client.getBulk(list);
接下來講說,用戶信息怎麼放入memcached中。主要利用HttpSessionListener和HttpSessionAttributeListener來監聽對Session的操做。
很少寫了,貼上代碼就很清楚了:
Java代碼
- HttpSessionAttributeListener:
- public void attributeReplaced(HttpSessionBindingEvent event) {
- HttpSession session = event.getSession();
- if(SESSION_KEY.endsWith(event.getName())) {
- MemcachedClient client = (MemcachedClient)SpringBeanHolder.getBean(MEMCACEHD_BEAN_NAME);
- SessionContext context = (SessionContext)session.getAttribute(SESSION_KEY);
-
- String username = "";
- try {
- username = context.getDocument().getElementsByTagName("operatorName").item(0).getFirstChild().getNodeValue();
-
- String json = "{username: '"+username+"'}";
- client.set(context.getUserID(), session.getMaxInactiveInterval(), json);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
-
- public class UserSessionListener implements HttpSessionListener {
- private static final String SESSION_KEY = "sessionContext";
- private static final String MEMCACEHD_BEAN_NAME = "memcachedClient";
-
- public void sessionCreated(HttpSessionEvent event) {
- }
-
- public void sessionDestroyed(HttpSessionEvent event) {
- HttpSession session = event.getSession();
- MemcachedClient client = (MemcachedClient)SpringBeanHolder.getBean(MEMCACEHD_BEAN_NAME);
-
- SessionContext context = (SessionContext)session.getAttribute(SESSION_KEY);
- System.out.println("【Destroy Session】 User:"+context.getUserID());
-
- //刪除對應用戶在memcached的數據
- client.delete(context.getUserID());
- }
-
- }