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());
- }
-
- }