Session 共享有多种解决方法,常用的有四种:客户端 Cookie 保存、服务器间 Session 同步、使用集群管理 Session(如本文要介绍的Memcached Session Manager) 、把 Session 持久化到数据库:
使用集群统一管理Session 提供一个集群保存 session 共享信息.其他应用统统把自己的 session 信息存放到 session 集群服务器组。当应用系统需要 session 信息的时候直接到 session 集群服务器上读取。目前大多都是使用 Memcache 来对 Session 进行存储。 以 Memcache 来实现 Session 共享的方式目前比较流行的有两种实现方案,下面主要对这两种方案进行介绍。
使用Filter方式: 此方式使用过滤器的方式重新对httpRequest 对象进行了包装,并加入memcached客户端,此方式的优点是:使用简单,把过滤器配置进去即可,另外比较灵活,因为它是在客户端实现的,配置比较灵活,而且服务器无关,你可以在任何支持servlet的容器上部署。
使用memcached-session-manager方式: memcached-session-manager,俗称 MSM ,是一个用于解决分布式 tomcat 环境下 session 共享的问题的开源解决方案。它的实现原理为以tomcat插件的方式部署在服务器,修改了 servlet 容器代码中的 session 相关代码,使其连接 memcached ,在 memcached 中创建和更新session。MSM拥有如下特性:
支持Tomcat6、Tomcat7、Tomcat8
支持黏性、非黏性 Session
无单一故障点
可处理 tomcat 故障转移
可处理 memcached 故障转移
插件式 session 序列化
允许异步保存 session ,以提升响应速度
只有当 session 有修改时,才会将 session 写回 memcached
JMX 管理&监控
Memcached-session-manager 支持tomcat6、tomcat7、tomcat8 ,利用 Value(Tomcat 阀)对 Request 进行跟踪。 Request 请求到来时,从 memcached 加载 session , Request 请求结束时,将 tomcat session 更新至 memcached ,以达到 session 共享之目的, 支持 sticky 和 non-sticky 模式。
优点:开发者不用考虑session共享的问题了,可以专注于业务逻辑开发,像正常使用 session 那样使用就完事了。不用显示编写代码,只需要对服务器进行配置即可使用。
缺点:如果你想改变session策略的话,必须重新部署每个服务器的servlet容器。
我们都知道对于一些比较大型的网站,在正式部署时一般是部署在不同故障域的多台应用服务器上,以 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。
首先谈下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
注意每台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/
由于nginx需要安装sticky session,故需要在linux环境下完成
http://repo1.maven.org/maven2/de/javakaffee/msm/
),需要根据tomcat的版本下载相应版本的msm,由于用到了memcached,因此还要用到memcached的java api包,```http://mvnrepository.com/artifact/de.javakaffee.msm/memcached-session-manager 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 http://mvnrepository.com/artifact/com.googlecode/reflectasm/1.01
```
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的$CATALINA_HOME/conf/server.xml
name="Catalina">
port="8001" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
port="8009" protocol="AJP/1.3" redirectPort="8443" />
name="Catalina" defaultHost="localhost" jvmRoute="tomcat1">
className="org.apache.catalina.realm.LockOutRealm">
className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
name="localhost"
unpackWARs="true" autoDeploy="true">
className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log." suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
docBase="E:/HPCWorkFolder/ServerCluster/nginx-tomcat-memcached-manager-session/www/webapptomcat1" path="" reloadable="true" />
tomcat1 Engine标签的jvmRoute属性值配置为tomcat1
name="Catalina">
port="8002" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
port="8019" protocol="AJP/1.3" redirectPort="8443" />
name="Catalina" defaultHost="localhost" jvmRoute="tomcat2">
className="org.apache.catalina.realm.LockOutRealm">
className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
name="localhost"
unpackWARs="true" autoDeploy="true">
className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log." suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
docBase="E:/HPCWorkFolder/ServerCluster/nginx-tomcat-memcached-manager-session/www/webapptomcat2" path="" reloadable="true" />
tomcat2 Engine标签的jvmRoute属性值配置为tomcat2
修改$CATALINA_HOME/conf/context.xml
tomcat1:
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"
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
<%=request.getSession(true).getId()%>
在tomcat2的web应用目录下放一个index.jsp文件,内容如下: tomcat2
<%=request.getSession(true).getId()%>
看到响应头中SetCookie
JSESSIONID=786579866D7914523416D9C35A3F74DB-n2.tomcat1 以及route(由nginx设置的)
看到响应头中已经没有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保持。