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保持。