基于MSM实现nginx负载均衡+tomcat集群+memcached(session共享)

↘锁芯ラ 提交于 2019-12-01 16:31:24

Session共享的方案介绍

Session 共享有多种解决方法,常用的有四种:客户端 Cookie 保存、服务器间 Session 同步、使用集群管理 Session(如本文要介绍的Memcached Session Manager) 、把 Session 持久化到数据库:

  1. 客户端 Cookie 保存 以cookie加密的方式保存在客户端.优点是减轻服务器端的压力,每次session信息被写在客服端,然后经浏览器再次提交到服务器。即使两次请求在集群中的两台服务器上完成,也可以到达session共享。

  2. 将 session 持久化到数据中 这种共享session的方式即将session信息存入数据库中,其它应用可以从数据库中查出 session 信息。目前采用这种方案时所使用的数据库一般为mysql。 利用数据库共享 session 的方案有一定的实用性,但也有如下缺点:首先 session 的并发读写在数据库中完成,对 mysql 的性能要求比较高;其次,我们需要额外地实现 session 淘汰(超时)逻辑代码,即定时从数据库表中更新和删除 session 信息,增加了工作量。

  3. 使用服务器间 session 同步 使用主-从服务器的架构,当用户在主服务器上登录后,通过脚本或者守护进程的方式,将 session 信息传递到各个从服务器中,这样用户访问其它的从服务器时,就可以读到session信息。 缺点:比如速度慢、不稳定等,另外,如果 session 信息传递是主->从单向的,会有一些风险,比如主服务器down了,其它服务器无法获得 session 信息

  4. 使用集群统一管理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容器。

为什么会有MSM(memcached-session-manager)

我们都知道对于一些比较大型的网站,在正式部署时一般是部署在不同故障域的多台应用服务器上,以 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 共享的问题的开源解决方案。

memcached-session-manager解决的问题

想象下web应用程序运行在多个tomcat,期望session能实现故障转移。你需要一个可扩展的方案-仅仅增加tomcat来处理更多的会话。该方案可通过memcached节点存储备份会话来实现。当一个tomcat负载过重或挂掉时,其他tomcat就会接管这个tomcat,从相应的memcached节点中获取会话数据,之后就可以服务这个会话。当然多个tomcat前面还需要有一个负载均衡,比如nginx。

memcached-session-manager是如何工作的

首先谈下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 sticky模式工作原理

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

集群的拓扑图

输入图片说明

为何在两个tomcat前面要放一个nginx

  1. 首先nginx可以作为两个tomcat的负载均衡,均衡两个tomat负载压力,负载均衡也可以使得客户端访问可以使用统一的url,如果没有nginx那么,访问tomcat1必须要用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 &quot;%r&quot; %s %b" /> <Context docBase="E:/HPCWorkFolder/ServerCluster/nginx-tomcat-memcached-manager-session/www/webapptomcat1" path="" reloadable="true" /> </Host> </Engine> </Service> ```

tomcat1 Engine标签的jvmRoute属性值配置为tomcat1

  1. tomcat2
  <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 &quot;%r&quot; %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,tomcat

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,配置成功了

nginx安装配置见

http://my.oschina.net/u/1167421/blog/604633

验证(memcached-session-manager是如何工作的)

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

参考资料

  1. memcached-session-manager源码及官方文档
  2. memcached-session-manager配置
  3. 使用memcached实现的tomcat高可用集群
  4. msm的jar包下载
  5. nginx服务器安装及配置文件详解
  6. 使用nginx sticky模块实现基于cookie的负载均衡
  7. 集群中几种session同步解决方案的比较
  8. MSM--Memcached_Session_Manager介绍及使用
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!