0%

tomcat + memcached + nginx 实现session共享

这里重点强调如何实现 linux 服务器上 服务器 session 共享,软件安装不再赘述。

首先我们需要对 cookie 和 session 的工作机制非常了解,如果不了解其中的原理,就算配置成功,也毫无意义。换了工作换了环境,重新配置起来仍然需要重头来过,事倍功半。

cookie 是怎样工作的?

例如,我们创建了一个名字为 login 的 Cookie 来包含访问者的信息,创建 Cookie 时,服务器端的 Header 如下面所示,这里假设访问者的注册名是“Michael Jordan”,同时还对所创建的 Cookie 的属性如 path、domain、expires 等进行了指定。

1
2
Set-Cookie:login=Michael Jordan;path=/;domain=msn.com;
expires=Monday,01-Mar-99 00:00:01 GMT

上面这个 Header 会自动在浏览器端计算机的 Cookie 文件中添加一条记录。浏览器将变量名为“login”的 Cookie 赋值为“Michael Jordon”。注意,在实际传递过程中这个 Cookie 的值是经过了 URLEncode 方法的 URL 编码操作的。

这个含有 Cookie 值的 HTTP Header 被保存到浏览器的 Cookie 文件后,Header 就通知浏览器将 Cookie 通过请求以忽略路径的方式返回到服务器,完成浏览器的认证操作。

此外,我们使用了 Cookie 的一些属性来限定该 Cookie 的使用。例如 Domain 属性能够在浏览器端对 Cookie 发送进行限定,具体到上面的例子,该 Cookie 只能传到指定的服务器上,而决不会跑到其他的 Web 站点上去。Expires 属性则指定了该 Cookie 保存的时间期限,例如上面的 Cookie 在浏览器上只保存到 1999 年 3 月 1 日 1 秒。

当然,如果浏览器上 Cookie 太多,超过了系统所允许的范围,浏览器将自动对它进行删除。至于属性 Path,用来指定 Cookie 将被发送到服务器的哪一个目录路径下。

说明:浏览器创建了一个 Cookie 后,对于每一个针对该网站的请求,都会在 Header 中带着这个 Cookie;不过,对于其他网站的请求 Cookie 是绝对不会跟着发送的。而且浏览器会这样一直发送,直到 Cookie 过期为止。

session 又是如何工作的?

由于 http 是无状态的协议,你访问了页面 A,然后在访问 B,http 无法确定这 2 个访问来自一个人,因此要用 cookie 或 session 来跟踪用户,根据授权和用户身份来显示不同的页面。比如用户 A 登陆了,那么能看到自己的个人信息,而 B 没登陆,无法看到个人信息。还有 A 可能在购物,把商品放入购物车,此时 B 也有这个过程,你无法确定 A,B 的身份和购物信息,所以需要一个 session ID 来维持这个过程。

cookie 是服务器发给客户端,并且保持在客户端的一个文件,里面包含了用户的访问信息(账户密码等),可以手动删除或设置有效期,在下次访问的时候,会返给服务器。注意:cookie 可以被禁用,所以要想其他办法,这就是 session。比如:你去商场购物,商场会给你办一张会员卡,下次你来出示该卡,会有打折优惠。该卡可以自己保存(cookie),或是商场代为保管,由于会员太多,个人需要保存卡号信息(session ID)。

为什么要持久化 SESSION?

在客户端每个用户的 Session 对象存在 Servlet 容器中,如果 Tomcat 服务器重起/当机的话该 session 就会丢失,而客户端的操作应为 session 的丢失而造成数据丢失,而且当前用户访问量巨大,每个用户的 Session 里存放大量的数据的话,那么就很占用服务器大量的内存,从而是服务器的性能受到影响。

番外篇

session 是非常重要的数据,非常宝贵。但是 session 同样也是缺憾满身,在大规模集群服务器中,最好使用其他替代方案。比如淘宝网。

对 nginx 的了解?

nginx 是一款非常优秀的服务器软件,其优秀程度这里不过多赘述。

进入正题

session 实现共享的方法分为多种

对于 WEB 应用集群的技术实现而言,最大的难点就是如何能在集群中的多个节点之间保持数据的一致性,会话(Session)信息是这些数据中最重要的一块。要实现这一点,大体上有两种方式,一种是把所有 Session 数据放到一台服务器上或者数据库中,集群中的所有节点通过访问这台 Session 服务器来获取数据;另一种就是在集群中的所有节点间进行 Session 数据的同步拷贝,任何一个节点均保存了所有的 Session 数据。

一、使用 tomcat 自身集群特性完成 session 共享

这种方式是使用 tomcat 自身广播的特点来进行 session 同步拷贝,优点是简单,缺点是一旦 tomcat 集群数量过多,很容易引发广播风暴。

详细配置 请参照 博客 https://zyycaesar.iteye.com/blog/296606

粗略配置如下 tomcat/server.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="8">
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"/>
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService"
address="224.0.0.0"
port="45564"
frequency="500"
dropTime="3000"/>
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="192.168.0.223"
port="4001"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
filter=".*\.gif;.*\.js;.*\.jpg;.*\.png;.*\.htm;.*\.html;.*\.css;.*\.txt;"/>
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
<ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
二、使用数据库持久化 session

这里又分为物理数据库和内存数据库物理数据库备份 session,由于其性能的原因,这里不作介绍。

内存数据库 可以暂分为 redis 和 memcached

这里我们只介绍 memcached,多个 tomcat 各种序列化策略配置如下:

  • java 默认序列化 tomcat 配置

conf/context.xml 添加

1
2
3
4
5
6
7
8
9
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
memcachedNodes="n1:192.168.100.208:11211 n2:192.168.100.208:11311"
lockingMode="auto"
sticky="false"
requestUriIgnorePattern= ".*\.(png|gif|jpg|css|js)$"
sessionBackupAsync= "false"
sessionBackupTimeout= "100"
transcoderFactoryClass="de.javakaffee.web.msm.JavaSerializationTranscoderFactory"
/>

lib 增加 jar 包:

1
2
3
spymemcached-2.10.3.jar
memcached-session-manager-1.7.0.jar
memcached-session-manager-tc7-1.7.0.jar
  • javolution 序列化 tomcat 配置

conf/context.xml 添加

1
2
3
4
5
6
7
8
9
10
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
memcachedNodes="n1:192.168.100.208:11211 n2:192.168.100.208:11311"
lockingMode="auto"
sticky="false"
requestUriIgnorePattern= ".*\.(png|gif|jpg|css|js)$"
sessionBackupAsync= "false"
sessionBackupTimeout= "100"
copyCollectionsForSerialization="true"
transcoderFactoryClass="de.javakaffee.web.msm.serializer.javolution.JavolutionTranscoderFactory"
/>

lib 增加 jar 包

1
2
3
4
5
6
7
msm-javolution-serializer-cglib-1.3.0.jar
msm-javolution-serializer-jodatime-1.3.0.jar
spymemcached-2.10.3.jar
javolution-5.4.3.1.jar
msm-javolution-serializer-1.7.0.jar
memcached-session-manager-1.7.0.jar
memcached-session-manager-tc7-1.7.0.jar
  • xstream 序列化 tomcat 配置

conf/context.xml 添加

1
2
3
4
5
6
7
8
9
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
memcachedNodes="n1:192.168.100.208:11211 n2:192.168.100.208:11311"
lockingMode="auto"
sticky="false"
requestUriIgnorePattern= ".*\.(png|gif|jpg|css|js)$"
sessionBackupAsync= "false"
sessionBackupTimeout= "100"
transcoderFactoryClass="de.javakaffee.web.msm.serializer.xstream.XStreamTranscoderFactory"
/>

lib 增加 jar 包

1
2
3
4
5
6
7
xmlpull-1.1.3.1.jar
xpp3_min-1.1.4c.jar
xstream-1.4.6.jar
msm-xstream-serializer-1.7.0.jar
spymemcached-2.10.3.jar
memcached-session-manager-1.7.0.jar
memcached-session-manager-tc7-1.7.0.jar
  • flexjson 序列化 tomcat 配置

conf/context.xml 添加

1
2
3
4
5
6
7
8
9
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
memcachedNodes="n1:192.168.100.208:11211 n2:192.168.100.208:11311"
lockingMode="auto"
sticky="false"
requestUriIgnorePattern= ".*\.(png|gif|jpg|css|js)$"
sessionBackupAsync= "false"
sessionBackupTimeout= "100"
transcoderFactoryClass="de.javakaffee.web.msm.serializer.json.JSONTranscoderFactory"
/>

lib 增加 jar 包

1
2
3
4
5
flexjson-3.1.jar
msm-flexjson-serializer-1.7.0.jar
spymemcached-2.10.3.jar
memcached-session-manager-1.7.0.jar
memcached-session-manager-tc7-1.7.0.jar
  • kryo 序列化 tomcat 配置

conf/context.xml 添加

1
2
3
4
5
6
7
8
9
10
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
memcachedNodes="n1:192.168.100.208:11211 n2:192.168.100.208:11311"
lockingMode="auto"
sticky="false"
requestUriIgnorePattern= ".*\.(png|gif|jpg|css|js)$"
sessionBackupAsync= "false"
sessionBackupTimeout= "100"
copyCollectionsForSerialization="true"
transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
/>

lib 增加 jar 包

1
2
3
4
5
6
7
8
9
kryo-1.04.jar
minlog-1.2.jar
asm-3.2.jar
reflectasm-1.01.jar
kryo-serializers-0.11.jar
msm-kryo-serializer-1.7.0.jar
spymemcached-2.10.3.jar
memcached-session-manager-1.7.0.jar
memcached-session-manager-tc7-1.7.0.jar

官网介绍说 使用 kryo 序列化 tomcat 的效率最高 所以这里只介绍 kryo 序列化。具体效率对比,还需要进一步验证。

修改 tomcat/context.xml 文件 新增如下代码

1
2
3
4
5
6
7
8
9
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
memcachedNodes="n1:192.168.0.216:11211"
sticky="false"
storageKeyPrefix="context"
sessionBackupAsync="false"
lockingMode="uriPattern:/path1|/path2"
requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
/>

Manager 各参数说明:

className 必选项,可配置为

1
de.javakaffee.web.msm.MemcachedBackupSessionManager和de.javakaffee.web.msm.DummyMemcachedBackupSessionManager。

其中 DummyMemcachedBackupSessionManager 可用于测试环境,不需要真实存在 memcached。

memcachedNodes 必选项,memcached 的节点信息,格式如:

1
memcachedNodes="n1:app01:11211,n2:app02:11211"。

failoverNodes

可选项,不能使用在 non-sticky sessions 模式。故障转移配置节点,多个使用空格或逗号分开,配置某个节点为备份节点,当其他节点都不可用时才会存储到备份节点,官方建议配置为和 tomcat 同服务器的节点。

理由如下:

假如有两台服务器 m1,m2,其中 m1 部署 tomcat 和 memcached 节点 n1,m2 部署 memcached 节点 n2。

如果配置 tomcat 的 failoverNodes 值为 n2 或者不配置,则当服务器 m1 挂掉后 n1 和 tomcat 中保存的 session 会丢失,而 n2 中未保存或者只保存了部分 session,这就造成部分用户状态丢失。

如果配置 tomcat 的 failoverNodes 值为 n1,则当 m1 挂掉后因为 n2 中保存了所有的 session,所以重启 tomcat 的时候用户状态不会丢失。

为什么 n2 中保存了所有的 session? 因为 failoverNodes 配置的值是 n1,只有当 n2 节点不可用时才会把 session 存储到 n1,所以这个时候 n1 中是没有保存任何 session 的。

username 可选项,SASL 的用户名,如果节点保护 membase 的 bucket uri。

password 可选项,和 username 搭配使用。

memcachedProtocol 可选项,默认 text,memcached 使用的协议,可选值:text,binary。

sticky 可选项,默认 true,制定是否使用粘性 session 模式。

说道 sticky 需要简单介绍

Sticky 模式:

tomcat session 为 主 session, memcached 为备 session。Request 请求到来时, 从 memcached 加载备 session 到 tomcat (仅当 tomcat jvmroute 发生变化时,否则直接取 tomcat session);Request 请求结束时,将 tomcat session 更新至 memcached,以达到主备同步之目的。

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 。以达到主备同步之目的。

多台 tomcat 集群时 需要选择 Non-Sticky 模式,即 sticky=”false”

需要使用到得 jar 列表如下

1
2
3
4
5
6
7
8
9
10
couchbase-client-1.2.2.jar
kryo-1.03.jar
kryo-serializers-0.10.jar
memcached-2.5.jar
memcached-session-manager-1.6.5.jar
memcached-session-manager-tc7-1.6.5.jar
minlog-1.2.jar
msm-kryo-serializer-1.6.5.jar
reflectasm-0.9.jar
spymemcached-2.10.2.jar

如果你查看过源码,你就会发现,如果当你调试不成功,session 不共享,一般都是 memcached-session-manager-1.6.5.jar、memcached-session-manager-tc7-1.6.5.jar、msm-kryo-serializer-1.6.5.jar 这三个 jar 包出问题。所以版本也很重要。

服务器之间的时间戳一致也非常重要,因为时间不一致将直接导致 session 过期。

memcached 的启动所有者最好设置为 nobody

本博文是经过实际电商项目验证,实际登录验证,验证过程中不断关停一些 tomcat,所以本博文可作为真实参考。

验证项目为大红袜全球购。

lockingMode 可选值,默认 none,只对 non-sticky 有效。

requestUriIgnorePattern 可选值,制定忽略那些请求的 session 操作,一般制定静态资源如 css,js 一类的。

sessionBackupAsync 可选值,默认 true,是否异步的方式存储到 memcached。

backupThreadCount 可选项,默认是 cpu 核心数,异步存储 session 的线程数。

sessionBackupTimeout 可选项,默认 100 毫秒,异步存储 session 的超时时间。

operationTimeout 可选项,默认 1000 毫秒,memcached 的操作超时时间。

storageKeyPrefix 可选值,默认值 webappVersion,存储到 memcached 的前缀,主要是为了区分多个 webapp 共享 session 的情况。可选值:静态字符串、host、context、webappVersion,多个使用逗号分割。

sessionAttributeFilter 可选值,通过正则表达式确定那些 session 中的属性应该被存储到 memcached。例子如:sessionAttributeFilter=”^(userName|sessionHistory)$“。

transcoderFactoryClass 可选值,默认值 de.javakaffee.web.msm.JavaSerializationTranscoderFactory,制定序列化和反序列化数据到 memcached 的工厂类。

使用 filter 方法存储

这种方法比较推荐,因为它的服务器使用范围比较多,不仅限于 tomcat ,而且实现的原理比较简单容易控制。

可以使用 memcached-session-filter

官方网址:https://code.google.com/p/memcached-session-filter/

官方介绍:解决集群环境下 java web 容器 session 共享,使用 filter 拦截器和 memcached 实现。在 tomcat 6 和 websphere 8 测试通过,现网并发 2000,日 PV 量 1100 万。

暂不支持 session event 包括 create destory 和 attribute change 东西很不错,体积很小,不过这个东东要和 spring 一起使用,而且要求存储到 memcached 的对象要实现 java 的序列化接口大家也知道,java 本身的序列化性能也很一般。

将其简单扩展,就可以不再依赖 spring ,并且利用 javolution 实现序列化,缓存的对象不再有限制。