Tomcat集群环境下session共享方案梳理(1)-通过memcached(MSM)方法实现

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

Tomcat集群session同步方案有以下几种方式: 1)使用tomcat自带的cluster方式,多个tomcat间自动实时复制session信息,配置起来很简单。但这个方案的效率比较低,在大并发下表现并不好。 2)利用nginx的基于访问ip的hash路由策略,保证访问的ip始终被路由到同一个tomcat上,这个配置更简单。但如果应用是某一个局域网大量用户同时登录,这样负载均衡就没什么作用了。 3)利用nginx插件实现tomcat集群和session同步,nginx-upstream-jvm-route-0.1.tar.gz,是一个Nginx的扩展模块,用来实现基于Cookie的Session Sticky的功能。 4)利用memcached实现(MSM工具)。memcached存储session,并把多个tomcat的session集中管理,前端在利用nginx负载均衡和动静态资源分离,在兼顾系统水平扩展的同时又能保证较高的性能。 5)利用redis实现。使用redis不仅仅可以将缓存的session持久化,还因为它支持的单个对象比较大,而且数据类型丰富,不只是缓存 session,还可以做其他用途,可以一举几得。 6)利用filter方法实现。这种方法比较推荐,因为它的服务器使用范围比较多,不仅限于tomcat ,而且实现的原理比较简单容易控制。 7)利用terracotta服务器共享session。这种方式配置比较复杂。

在Tomcat集群中,当一个节点出现故障,虽然有高可用集群来负责故障转移,但用户的session信息如何保持呢? 下面介绍第4种方案,session复制同步使用MSM(Memcache-Session-Manager),即利用MSM+Memcached做Session共享。

MSM介绍:(详细介绍可以参考:http://www.cnblogs.com/kevingrace/p/6401025.html) MSM是一个高可用的Tomcat Session共享解决方案,除了可以从本机内存快速读取Session信息(仅针对黏性Session)外,还可使用Memcached存取Session,以实现高可用。 传统tomcat集群,会话复制随着结点数增多,扩展性成为瓶颈。MSM使用memcached完成统一管理tomcat会话,避免tomcat结点间过多会话复制。 MSM利用Value(Tomcat 阀)对Request进行跟踪。Request请求到来时,从memcached加载session,Request请求结束时,将tomcat session更新至memcached,以达到session共享之目的, 支持sticky和non-sticky模式: sticky : 会话粘连模式(黏性session)。客户端在一台tomcat实例上完成登录后,以后的请求均会根据IP直接绑定到该tomcat实例。 no-sticky:会话非粘连模式(非粘性session)。客户端的请求是随机分发,多台tomcat实例均会收到请求。

在进行环境部署之前,要对cookie和session的工作机制非常了解,如果不了解其中的原理且只是机械性地去按照参考文档部署,那么这是毫无意义的。 a)cookie是怎么工作的? 加入我们创建了一个名字为login的Cookie来包含访问者的信息,创建Cookie时,服务器端的Header如下面所示,这里假设访问者的注册名是“wangshibo”,同时还对所创建的Cookie的属性如path、domain、expires等进行了指定。

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

上面这个Header会自动在浏览器端计算机的Cookie文件中添加一条记录。浏览器将变量名为“login”的Cookie赋值为“wangshibo”。 注意,在实际传递过程中这个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过期为止。

b)session是如何工作的? 由于http是无状态的协议,你访问了页面A,然后再访问B页面,http无法确定这2个访问来自一个人,因此要用cookie或session来跟踪用户,根据授权和用户身份来 显示不同的页面。比如用户A登陆了,那么能看到自己的个人信息,而B没登陆,无法看到个人信息。还有A可能在购物,把商品放入购物车,此时B也有这个过程, 你无法确定A,B的身份和购物信息,所以需要一个session ID来维持这个过程。 cookie是服务器发给客户端并保持在客户端的一个文件,里面包含了用户的访问信息(账户密码等),可以手动删除或设置有效期,在下次访问的时候,会返给服务器。 注意:cookie可以被禁用,所以要想其他办法,这就是session。cookie数据存放在客户的浏览器上,session数据放在服务器上。cookie同时也是session id的载体,cookie保存session id。另外:cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session。session是服务器端缓存,cookie是客户端缓存。所以建议:将登陆信息等重要信息存放为session;其他信息如果需要保留,可以放在cookie中, 比如:你去商场购物,商场会给你办一张会员卡,下次你来出示该卡,会有打折优惠,该卡可以自己保存(cookie),或是商场代为保管,由于会员太多,个人需要保存卡号信息(session ID)。

为什么要持久化session(共享session)? 因为:在客户端每个用户的Session对象存在Servlet容器中,如果Tomcat服务器重启或者宕机的话,那么该session就会丢失,而客户端的操作会由于session丢失而造成数据丢失;如果当前用户访问量巨大,每个用户的Session里存放大量数据的话,那么就很占用服务器大量的内存,进而致使服务器性能受到影响。 可以使用数据库持久化session,分为物理数据库和内存数据库。物理数据库备份session,由于其性能原因,不推荐;内存数据库可以使用redis和memcached,这里介绍memcached的方法。

MSM工作原理: a)Sticky Session(黏性) 模式下的工作原理: Tomcat本地Session为主Session,Memcached 中的Session为备Session。 安装在Tomcat上的MSM使用本机内存保存Session,当一个请求执行完毕之后,如果对应的Session在本地不存在(即某用户的第一次请求),则将该Session复制一份至Memcached;当该Session的下一个请求到达时,会使用Tomcat的本地Session,请求处理结束之后,Session的变化会同步更新到 Memcached,保证数据一致。 当集群中的一个Tomcat挂掉,下一次请求会被路由到其他Tomcat上。负责处理此此请求的Tomcat并不清楚Session信息,于是从Memcached查找该Session,更新该Session并将其保存至本机。此次请求结束,Session被修改,送回Memcached备份。 b)Non-sticky Session (非黏性)模式下的工作原理(记住:多台tomcat集群或多个tomcat实例时需要选择Non-Sticky模式,即sticky="false"): Tomcat本地Session为中转Session,Memcached为主备Session。 收到请求,加载备Session至本地容器,若备Session加载失败则从主Session加载; 请求处理结束之后,Session的变化会同步更新到Memcached,并清除Tomcat本地Session。

------------------------------------------------------------------------------------------------------------ 废话不多说了,下面直接看nginx+memcached+tomcat实现session共享的集群操作记录:: 一、集群实验环境信息

主机            端口    开源软件
192.168.1.118   80      nginx(nginx-1.8.1)
192.168.1.118   8081    tomcat1(版本为tomcat7)
192.168.1.118   8091    tomcat2
192.168.1.118   11211   memcached(memcached-1.4.34)
192.168.1.118   11212   memcached(memcached-1.4.34)

服务器系统均是centos6.8

我这里是在一台测试服务器(192.168.1.118/110.110.115.118)上操作的,即nginx、memcached、tomcat均是部署在同一台服务器上,只是为了测试。
如果在生产环境下,ngixn、tocmat、memcached应该是部署到不同的服务器上。

为什么在两个tomcat实例前要放一个nginx?
1)nginx可以作为两个tomcat的负载均衡,均衡两个tomat负载压力,负载均衡也可以使得客户端访问可以使用统一的url,如果没有nginx,那么访问tomcat1必须要用http://localhost:8081/,而访问tomcat2需要用http://localhost:8091/
2)nginx处理静态资源的性能比tomcat好很多

实验拓扑图:

二、nginx安装

1)安装依赖包
[root@linux-node3 ~]# yum -y install gcc gcc-c++

2)安装pcre库
[root@linux-node3 ~]# cd /usr/local/src/
[root@linux-node3 src]# wget https://jaist.dl.sourceforge.net/project/pcre/pcre/8.37/pcre-8.37.tar.gz
[root@linux-node3 src]# tar -zvxf pcre-8.37.tar.gz
[root@linux-node3 src]# cd pcre-8.37
[root@linux-node3 pcre-8.37]# ./configure && make && make install

3)安装zlib库
[root@linux-node3 src]# wget http://www.zlib.net/zlib-1.2.11.tar.gz
[root@linux-node3 src]# tar -zvxf zlib-1.2.11.tar.gz 
[root@linux-node3 zlib-1.2.11]# ./configure && make && make install

4)安装openssl
[root@linux-node3 src]# wget http://www.openssl.org/source/openssl-1.0.1c.tar.gz
[root@linux-node3 src]# tar -zvxf openssl-1.0.1c.tar.gz && cd openssl-1.0.1c && ./config && make && make install

5)安装nginx
特别注意要指定prce zlib openssl原码包位置
[root@linux-node3 src]# wget http://nginx.org/download/nginx-1.8.1.tar.gz
[root@linux-node3 src]# tar -zvxf nginx-1.8.1.tar.gz 
[root@linux-node3 src]# cd nginx-1.8.1
[root@linux-node3 nginx-1.8.1]# ./configure --prefix=/usr/local/nginx --with-http_ssl_module --with-pcre=/usr/local/src/pcre-8.37 --with-zlib=/usr/local/src/zlib-1.2.11  --with-openssl=/usr/local/src/openssl-1.0.1c
[root@linux-node3 nginx-1.8.1]# make && make install

6)安装成功后配置nginx
[root@linux-node3 nginx-1.8.1]# vim /usr/local/nginx/conf/nginx.conf
#user  nobody;
worker_processes  8;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;

worker_rlimit_nofile 65535;
events {
   use epoll;
    worker_connections  65535;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    charset utf-8;
        
    ######
    ## set access log format
    ######
    log_format  main  '$http_x_forwarded_for $remote_addr $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_cookie" $host $request_time';

    #######
    ## http setting
    #######
    sendfile       on;
    tcp_nopush     on;
    tcp_nodelay    on;
    keepalive_timeout  65;
    fastcgi_connect_timeout 30000;
    fastcgi_send_timeout 30000;
    fastcgi_read_timeout 30000;
    fastcgi_buffer_size 256k;
    fastcgi_buffers 8 256k;
    fastcgi_busy_buffers_size 256k;
    fastcgi_temp_file_write_size 256k;
    fastcgi_intercept_errors on;

    ##cache##
    client_header_timeout 60s;
    client_body_timeout 60s;
    client_max_body_size 10m;
    client_body_buffer_size 1m;
    proxy_connect_timeout 5;
    proxy_read_timeout 60;
    proxy_send_timeout 5;               
    proxy_buffer_size 64k;
    proxy_buffers 4 128k;
    proxy_busy_buffers_size 128k;
    proxy_temp_file_write_size 1m;
    proxy_temp_path /home/temp_dir;
    proxy_cache_path /home/cache levels=1:2 keys_zone=cache_one:200m inactive=1d max_size=30g;
    ##end##

    gzip  on;
    gzip_min_length  1k;
    gzip_buffers     4 16k;
    gzip_http_version 1.1;
    gzip_comp_level 9;
    gzip_types       text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php;
    gzip_vary on;

    ## includes vhosts
    include vhosts/*.conf;
}

[root@linux-node3 nginx-1.8.1]# cd /usr/local/nginx/conf/
[root@linux-node3 conf]# mkdir vhosts
[root@linux-node3 conf]# cd vhosts/
[root@linux-node3 vhosts]# vim test.conf
      upstream tomcat-lb {
      server 127.0.0.1:8081;
      server 127.0.0.1:8091;
      }
                  
      server {
      listen  80;
      server_name www.wangshibo.com; 
      location / {
          proxy_pass http://tomcat-lb;
          proxy_set_header  X-Real-IP $remote_addr;
          proxy_set_header  REMOTE-HOST $remote_addr;
          proxy_set_header  Host $host;
          proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
        }
        location ~ .*\.(gif|jpg|png|htm|html|css|ico|flv|swf)(.*) {
              proxy_pass http://tomcat-lb;
              proxy_redirect off;
              proxy_set_header Host $host;
              proxy_cache cache_one;
              proxy_cache_valid 200 302 1h;
              proxy_cache_valid 301 1d;
              proxy_cache_valid any 10m;
              expires 30d;
              proxy_cache_key $host$uri$is_args$args;
        }
}

三、memcached安装

[root@linux-node3 src]# yum -y install libevent libevent-devel
[root@linux-node3 src]# wget http://memcached.org/files/memcached-1.4.34.tar.gz
[root@linux-node3 src]# tar -zvxf memcached-1.4.34.tar.gz
[root@linux-node3 src]# cd memcached-1.4.34
[root@linux-node3 memcached-1.4.34]# ./configure --prefix=/usr/local/memcached
[root@linux-node3 memcached-1.4.34]# make && make install
 
启动memcached,端口11211可以根据自己需要修改不同端口
[root@linux-node3 memcached-1.4.34]# /usr/local/memcached/bin/memcached -d -m 512 -u root -p 11211 -c 1024 -P /var/lib/memcached.11211pid
[root@linux-node3 memcached-1.4.34]# /usr/local/memcached/bin/memcached -d -m 512 -u root -p 11212 -c 1024 -P /var/lib/memcached.11212pid
 
[root@linux-node3 memcached-1.4.34]# ps -ef|grep memcached
root     44007     1  0 14:21 ?        00:00:00 /usr/local/memcached/bin/memcached -d -m 512 -u root -p 11211 -c 1024 -P /var/lib/memcached.11211pid
root     44018     1  0 14:21 ?        00:00:00 /usr/local/memcached/bin/memcached -d -m 512 -u root -p 11212 -c 1024 -P /var/lib/memcached.11212pid
root     44038 21647  0 14:22 pts/2    00:00:00 grep  memcached
 
测试一下memcached连接,如下说明成功(输入quit退出)
[root@linux-node3 ~]# telnet 192.168.1.118 11211
Trying 192.168.1.118...
Connected to 192.168.1.118.
Escape character is '^]'.

[root@linux-node3 ~]# telnet 192.168.1.118 11212
Trying 192.168.1.118...
Connected to 192.168.1.118.
Escape character is '^]'.

四、安装jdk

查看CentOS自带JDK是否已安装
[root@linux-node3 ~]# yum list installed |grep java
[root@linux-node3 ~]# 

若有自带安装的JDK,如何卸载CentOS系统自带Java环境?
卸载JDK相关文件输入:yum -y remove java-1.7.0-openjdk*。
卸载tzdata-java输入:yum -y remove tzdata-java.noarch。
当结果显示为Complete!即卸载完毕。
注:"*"表示卸载掉java 1.7.0的所有openjdk相关文件。

查看yum库中的java安装包
[root@linux-node3 ~]# yum -y list java*
........
java-1.5.0-gcj.x86_64                                                      1.5.0.0-29.1.el6                                             base   
java-1.5.0-gcj-devel.x86_64                                                1.5.0.0-29.1.el6                                             base   
java-1.5.0-gcj-javadoc.x86_64                                              1.5.0.0-29.1.el6                                             base   
java-1.5.0-gcj-src.x86_64                                                  1.5.0.0-29.1.el6                                             base   
java-1.6.0-openjdk.x86_64                                                  1:1.6.0.41-1.13.13.1.el6_8                                   updates
java-1.6.0-openjdk-demo.x86_64                                             1:1.6.0.41-1.13.13.1.el6_8                                   updates
java-1.6.0-openjdk-devel.x86_64                                            1:1.6.0.41-1.13.13.1.el6_8                                   updates
java-1.6.0-openjdk-javadoc.x86_64                                          1:1.6.0.41-1.13.13.1.el6_8                                   updates
java-1.6.0-openjdk-src.x86_64                                              1:1.6.0.41-1.13.13.1.el6_8                                   updates
java-1.7.0-openjdk.x86_64                                                  1:1.7.0.131-2.6.9.0.el6_8                                    updates
java-1.7.0-openjdk-demo.x86_64                                             1:1.7.0.131-2.6.9.0.el6_8                                    updates
java-1.7.0-openjdk-devel.x86_64                                            1:1.7.0.131-2.6.9.0.el6_8                                    updates
java-1.7.0-openjdk-javadoc.noarch                                          1:1.7.0.131-2.6.9.0.el6_8                                    updates
java-1.7.0-openjdk-src.x86_64                                              1:1.7.0.131-2.6.9.0.el6_8                                    updates
java-1.8.0-openjdk.x86_64                                                  1:1.8.0.121-0.b13.el6_8                                      updates
java-1.8.0-openjdk-debug.x86_64                                            1:1.8.0.121-0.b13.el6_8                                      updates
java-1.8.0-openjdk-demo.x86_64                                             1:1.8.0.121-0.b13.el6_8                                      updates
java-1.8.0-openjdk-demo-debug.x86_64                                       1:1.8.0.121-0.b13.el6_8                                      updates
java-1.8.0-openjdk-devel.x86_64                                            1:1.8.0.121-0.b13.el6_8                                      updates
java-1.8.0-openjdk-devel-debug.x86_64                                      1:1.8.0.121-0.b13.el6_8                                      updates
java-1.8.0-openjdk-headless.x86_64                                         1:1.8.0.121-0.b13.el6_8                                      updates
java-1.8.0-openjdk-headless-debug.x86_64                                   1:1.8.0.121-0.b13.el6_8                                      updates
java-1.8.0-openjdk-javadoc.noarch                                          1:1.8.0.121-0.b13.el6_8                                      updates
java-1.8.0-openjdk-javadoc-debug.noarch                                    1:1.8.0.121-0.b13.el6_8                                      updates
java-1.8.0-openjdk-src.x86_64                                              1:1.8.0.121-0.b13.el6_8                                      updates
java-1.8.0-openjdk-src-debug.x86_64                                        1:1.8.0.121-0.b13.el6_8                                      updates
........

以yum库中java-1.7.0为例
注:"*"表示将java-1.7.0的所有相关Java程序都安装上。
[root@linux-node3 ~]# yum -y install java-1.7.0-openjdk*

查看刚安装的Java版本信息。
输入:java -version 可查看Java版本;
输入:javac 可查看Java的编译器命令用法

[root@linux-node3 ~]# java -version
java version "1.7.0_131"
OpenJDK Runtime Environment (rhel-2.6.9.0.el6_8-x86_64 u131-b00)
OpenJDK 64-Bit Server VM (build 24.131-b00, mixed mode)

[root@linux-node3 ~]# which java
/usr/bin/java
[root@linux-node3 ~]# ll /usr/bin/java
lrwxrwxrwx. 1 root root 22 Feb 14 22:25 /usr/bin/java -> /etc/alternatives/java
[root@linux-node3 ~]# ll /etc/alternatives/java
lrwxrwxrwx. 1 root root 46 Feb 14 22:25 /etc/alternatives/java -> /usr/lib/jvm/jre-1.7.0-openjdk.x86_64/bin/java
[root@linux-node3 ~]# cd /usr/lib/jvm
[root@linux-node3 jvm]# ll
total 4
lrwxrwxrwx. 1 root root   26 Feb 14 22:25 java -> /etc/alternatives/java_sdk
lrwxrwxrwx. 1 root root   32 Feb 14 22:25 java-1.7.0 -> /etc/alternatives/java_sdk_1.7.0
drwxr-xr-x. 9 root root 4096 Feb 14 22:25 java-1.7.0-openjdk-1.7.0.131.x86_64
lrwxrwxrwx. 1 root root   35 Feb 14 22:25 java-1.7.0-openjdk.x86_64 -> java-1.7.0-openjdk-1.7.0.131.x86_64
lrwxrwxrwx. 1 root root   34 Feb 14 22:25 java-openjdk -> /etc/alternatives/java_sdk_openjdk
lrwxrwxrwx. 1 root root   21 Feb 14 22:25 jre -> /etc/alternatives/jre
lrwxrwxrwx. 1 root root   27 Feb 14 22:25 jre-1.7.0 -> /etc/alternatives/jre_1.7.0
lrwxrwxrwx. 1 root root   39 Feb 14 22:25 jre-1.7.0-openjdk.x86_64 -> java-1.7.0-openjdk-1.7.0.131.x86_64/jre
lrwxrwxrwx. 1 root root   29 Feb 14 22:25 jre-openjdk -> /etc/alternatives/jre_openjdk

由上可知,java的home目录是/usr/lib/jvm/java-1.7.0-openjdk.x86_64

设置java的环境变量
[root@115 ~]# vim /etc/profile
.......
export JAVA_HOME=/usr/lib/jvm/java-1.7.0-openjdk.x86_64
export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$PATH:$JAVA_HOME/bin

[root@linux-node3 jvm]# source /etc/profile

五、安装与配置tomcat 1)安装tomcat [root@linux-node3 src]# wget http://apache.fayea.com/tomcat/tomcat-7/v7.0.75/bin/apache-tomcat-7.0.75.tar.gz [root@linux-node3 src]# tar -zvxf apache-tomcat-7.0.75.tar.gz  [root@linux-node3 src]# mv apache-tomcat-7.0.75 /usr/local/tomcat1

2)添加memcached和msm(Memcached_Session_Manager)的依赖jar包,如下:

asm-3.2.jar
kryo-1.03.jar
kryo-serializers-0.10.jar
memcached-session-manager-1.7.0.jar
memcached-session-manager-tc7-1.7.0.jar
minlog-1.2.jar
msm-kryo-serializer-1.6.3.jar
reflectasm-0.9.jar
spymemcached-2.7.3.jar

注意:memcached-session-manager-tc7-1.7.0.jar中的tc7为tomcat的版本号。一定要注意:不同版本号的tomcat,对应的msm包也不同。此处为tomcat7的jar包

上面依赖包下载地址:https://pan.baidu.com/s/1eRRncpO 提取密码:y56i

--------------------------------------------------------------------------------------------------------------------------------- msm相关版本的jar包下载地址:http://repo1.maven.org/maven2/de/javakaffee/msm/ spymemcached相关版本下载地址:http://repo1.maven.org/maven2/net/spy/spymemcached ---------------------------------------------------------------------------------------------------------------------------------

把依赖包下载后全部上传到tomcat1和tomcat2的安装路径下的lib/ 目录下

3)配置tomcat

这里我的tomcat1的服务端口用的是8081,所以需要将tomcat1的server.xml中默认的8080改成8081
[root@linux-node3 ~]# cd /usr/local/tomcat1/conf/
[root@linux-node3 conf]# cp server.xml server.xml.bak
[root@linux-node3 conf]# vim server.xml
.......
    <Connector port="8081" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
......
   <Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat1">  
......

注意:启有Engine标签中的jvmRoute属性,启用的目的就是为了区分session是在哪个节点上生成的
这个Engine name是非必选 ,只有选择了sticky模式才加入jvmRoute属性。这里为了实验效果,我们暂且选择这个设置项。
不同的tomcat实例jvmRoute取值不能相同。
例:8081端口的tomcat实例jvmRoute=tomcat1,8091端口的tomcat实例jvmRoute=tomcat2  

接下来进行序列化tomcat配置,修改conf/context.xml文件。 序列化tomcat配置的方法有很多种:java默认序列化tomcat配置、javolution序列化tomcat配置、xstream序列化tomcat配置、flexjson序列化tomcat配置和kryo序列化tomcat配置。官网介绍说 使用kryo 序列化tomcat的效率最高,所以这里只介绍kryo序列化。 在No-Stick模式和Stick模式下context.xml文件配置也有所不同(一般用的是No-Stick模式) a)No-Stick模式(记住:多台tomcat集群或多个tomcat实例时 需要选择Non-Sticky模式,即sticky="false")

[root@linux-node3 ~]# cd /usr/local/tomcat1/conf/
[root@linux-node3 conf]# cp context.xml context.xml.bak
[root@linux-node3 conf]# vim context.xml   //在<Context>和</Context>之间添加下面内容
   <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
            memcachedNodes="n1:192.168.1.118:11211 n2:192.168.1.118:11212"     
            lockingMode="auto"
            sticky="false" 
            requestUriIgnorePattern= ".*\.(png|gif|jpg|css|js)$"    
            sessionBackupAsync= "true"   
            sessionBackupTimeout= "1800000"      
            copyCollectionsForSerialization="true"
            transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"    
    />

然后将congtext.xml文件拷贝到tomcat2的相同路径下

b)Stick模式。故障转移配置节点(failoverNodes),不能使用在non-sticky sessions模式,多个使用空格或逗号分开,配置某个节点为备份节点,当其他节点都不可用时才会存储到备份节点,适用于sticky模式(即一台tomcat,多台memcached)。

[root@linux-node3 ~]# cd /usr/local/tomcat1/conf/
[root@linux-node3 conf]# cp context.xml context.xml.bak
[root@linux-node3 conf]# vim context.xml   //在<Context>和</Context>之间添加下面内容
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
        memcachedNodes="n1:192.168.1.118:11211 n2:192.168.1.118:11212"
        sticky="true" 
        failoverNodes="n2"
        requestUriIgnorePattern=".*\.(png|gif|jpg|css|js|swf|flv)$"
        transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
        copyCollectionsForSerialization="true"
    />
 
然后将congtext.xml文件拷贝到tomcat2的相同路径下,并将failoverNodes后面的参数改为n1

-------------------------------------------------------------------------------------------------------------- 注意:这里实验环境是开启了两个memcached端口,如果开启一个memcached端口也可以的,比如只开启11211端口,则No-Stick模式的配置如下:

   <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
            memcachedNodes="n1:192.168.1.118:11211"     
            sticky="false" 
            requestUriIgnorePattern= ".*\.(png|gif|jpg|css|js)$"    
            sessionBackupAsync= "true"   
            sessionBackupTimeout= "1800000"      
            copyCollectionsForSerialization="false"
            transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"    
    />

--------------------------------------------------------------------------------------------------------------

六、配置tomcat集群

[root@linux-node3 ~]# cd /usr/local/
[root@linux-node3 local]# cp -r tomcat1 tomcat2

修改server.xml文件里的相应端口,防止tomcat1和tomcat2端口冲突
[root@linux-node3 local]# vim tomcat2/conf/server.xml
.......
    <Server port="8006" shutdown="SHUTDOWN">       //把8005修改成8006
......
    <Connector port="8091" protocol="HTTP/1.1"     //把8081修改成8091根据自己来配置,修改后的8090与nginx配置一样
               connectionTimeout="20000"
               redirectPort="8443" />
.....
    <Connector port="8010" protocol="AJP/1.3" redirectPort="8443" />    //把8009修改成8010
.....
   <Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat2">   //把tomcat1改为tomcat2

七、启动tomcat集群 

[root@linux-node3 ~]# /usr/local/tomcat1/bin/startup.sh 
Using CATALINA_BASE:   /usr/local/tomcat1
Using CATALINA_HOME:   /usr/local/tomcat1
Using CATALINA_TMPDIR: /usr/local/tomcat1/temp
Using JRE_HOME:        /usr/lib/jvm/java-1.7.0-openjdk.x86_64
Using CLASSPATH:       /usr/local/tomcat1/bin/bootstrap.jar:/usr/local/tomcat1/bin/tomcat-juli.jar
Tomcat started.
[root@linux-node3 ~]# /usr/local/tomcat2/bin/startup.sh 
Using CATALINA_BASE:   /usr/local/tomcat2
Using CATALINA_HOME:   /usr/local/tomcat2
Using CATALINA_TMPDIR: /usr/local/tomcat2/temp
Using JRE_HOME:        /usr/lib/jvm/java-1.7.0-openjdk.x86_64
Using CLASSPATH:       /usr/local/tomcat2/bin/bootstrap.jar:/usr/local/tomcat2/bin/tomcat-juli.jar
Tomcat started.
[root@linux-node3 ~]# ps -ef|grep tomcat
root     29224     1 49 02:21 pts/2    00:00:04 /usr/lib/jvm/java-1.7.0-openjdk.x86_64/bin/java -Djava.util.logging.config.file=/usr/local/tomcat1/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -classpath /usr/local/tomcat1/bin/bootstrap.jar:/usr/local/tomcat1/bin/tomcat-juli.jar -Dcatalina.base=/usr/local/tomcat1 -Dcatalina.home=/usr/local/tomcat1 -Djava.io.tmpdir=/usr/local/tomcat1/temp org.apache.catalina.startup.Bootstrap start
root     29250     1 87 02:21 pts/2    00:00:04 /usr/lib/jvm/java-1.7.0-openjdk.x86_64/bin/java -Djava.util.logging.config.file=/usr/local/tomcat2/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -classpath /usr/local/tomcat2/bin/bootstrap.jar:/usr/local/tomcat2/bin/tomcat-juli.jar -Dcatalina.base=/usr/local/tomcat2 -Dcatalina.home=/usr/local/tomcat2 -Djava.io.tmpdir=/usr/local/tomcat2/temp org.apache.catalina.startup.Bootstrap start
root     29278 29157  0 02:21 pts/2    00:00:00 grep tomcat
[root@linux-node3 ~]# lsof -i:8080
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
java    29224 root   55u  IPv6 383017      0t0  TCP *:webcache (LISTEN)
[root@linux-node3 ~]# lsof -i:8090
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
java    29250 root   55u  IPv6 383056      0t0  TCP *:8090 (LISTEN)

八、启动nginx

[root@linux-node3 ~]# /usr/local/nginx/sbin/nginx 
[root@linux-node3 ~]# ps -ef|grep nginx
root     29282     1  0 02:23 ?        00:00:00 nginx: master process /usr/local/nginx/sbin/nginx
nobody   29283 29282  2 02:23 ?        00:00:00 nginx: worker process      
nobody   29284 29282  1 02:23 ?        00:00:00 nginx: worker process      
nobody   29285 29282  2 02:23 ?        00:00:00 nginx: worker process      
nobody   29286 29282  2 02:23 ?        00:00:00 nginx: worker process      
nobody   29287 29282  1 02:23 ?        00:00:00 nginx: worker process      
nobody   29288 29282  2 02:23 ?        00:00:00 nginx: worker process      
nobody   29289 29282  3 02:23 ?        00:00:00 nginx: worker process      
nobody   29290 29282  1 02:23 ?        00:00:00 nginx: worker process      
nobody   29291 29282  0 02:23 ?        00:00:00 nginx: cache manager process
nobody   29292 29282  0 02:23 ?        00:00:00 nginx: cache loader process
root     29294 29157  0 02:23 pts/2    00:00:00 grep nginx
[root@linux-node3 ~]# lsof -i:80
COMMAND   PID   USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
nginx   29282   root    6u  IPv4 383819      0t0  TCP *:http (LISTEN)
nginx   29283 nobody    6u  IPv4 383819      0t0  TCP *:http (LISTEN)
nginx   29284 nobody    6u  IPv4 383819      0t0  TCP *:http (LISTEN)
nginx   29285 nobody    6u  IPv4 383819      0t0  TCP *:http (LISTEN)
nginx   29286 nobody    6u  IPv4 383819      0t0  TCP *:http (LISTEN)
nginx   29287 nobody    6u  IPv4 383819      0t0  TCP *:http (LISTEN)
nginx   29288 nobody    6u  IPv4 383819      0t0  TCP *:http (LISTEN)
nginx   29289 nobody    6u  IPv4 383819      0t0  TCP *:http (LISTEN)
nginx   29290 nobody    6u  IPv4 383819      0t0  TCP *:http (LISTEN)

九、测试session是否共享

[root@linux-node3 ~]# cd /usr/local/tomcat1/webapps/ROOT/
[root@linux-node3 ROOT]# mkdir tomcat-session
[root@linux-node3 ROOT]# cd tomcat-session/
[root@linux-node3 tomcat-session]# vim index.jsp
<%@ page contentType="text/html; charset=GBK" %>
<%@ page import="java.util.*" %>
<html><head><title>Cluster Test</title></head>
<body>
<%
  //HttpSession session = request.getSession(true);
  System.out.println(session.getId());
  out.println("<br> SESSION ID:" + session.getId()+"<br>");
%>
</body>


将上面的tomcat-session拷贝到tomcat2的同路径下
[root@linux-node3 tomcat-session]# cp -r ../tomcat-session /usr/local/tomcat2/webapps/ROOT/

本机绑定hosts进行访问测试: 110.110.115.118 www.wangshibo.com

测试步骤(服务器上iptables防火墙开通相应web端口) a)访问http://www.wangshibo.com/tomcat-session,显示出Session ID值,说明nginx反向代理tomcat运行正常。 b)按F5刷新多次,看看Session ID是否会变化。如果Session ID指一直不变,说明Session ID共享成功;反之,共享不成功! c)关闭其中一个tomcat实例,观察页面的Session ID是否有变化,如果Session ID一直不变,则表示Session ID已经共享到其他tomcat实例上)了。

响应速度: MSM在Session管理时,Tomcat中会保持本地Session,往Memcached中的同步是异步完成的,所以访问速度和普通模式没什么区别。 唯一有区别的是,tomcat实例关闭后的首次访问时,响应速度会变慢,但是持续一小段时间后续访问速度恢复正常。

-------------------------------------------------扩展------------------------------------------------------ 以上介绍了nginx+tomcat+memcached实现session共享集群的操作记录,下面再追加一些需要注意的东西:

上面采用了效率最高的kryo序列化tomcat配置,当然还有其他四种序列化tomcat的配置方法,分别是:
a)java默认序列化tomcat配置,conf/context.xml添加:
<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包: 
spymemcached-2.10.3.jar
memcached-session-manager-1.7.0.jar
memcached-session-manager-tc7-1.7.0.jar

b)javolution序列化tomcat配置,conf/context.xml添加:
<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包: 
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

c)xstream序列化tomcat配置,conf/context.xml添加:
<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包: 
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

d)flexjson序列化tomcat配置,conf/context.xml添加:
<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包 
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

序列化tomcat配置中有关Manager各参数说明:

a)className
这个是必选项。可配置为:
de.javakaffee.web.msm.MemcachedBackupSessionManager
或者
de.javakaffee.web.msm.DummyMemcachedBackupSessionManager
其中DummyMemcachedBackupSessionManager可用于测试环境,不需要真实存在memcached
 
b)memcachedNodes
这个是必选项。memcached的节点信息(多个节点使用空格或逗号分开),格式如:
memcachedNodes="n1:app01:11211,n2:app02:11211"。
 
c)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的。

d)lockingMode 
这个是可选项,默认none,只对non-sticky有效。
当配置成node时,表示从来不加锁
当配置成all时,表示当请求时对Session锁定,直到请求结束
当配置成auto时,表示对只读的request不加锁,对非只读的request加锁

e)requestUriIgnorePattern 
这个是可选项,制定忽略那些请求的session操作,一般制定静态资源如css,js一类的。 

f)sessionBackupAsync 
这个是可选项,默认true,是否异步的方式存储到memcached。 

j)sessionBackupTimeout 
这个是可选项,默认100毫秒,异步存储session的超时时间。即web工程对session的修改更新到memcache上的时间。

h)copyCollectionsForSerialization 
这个是可选项,默认false。

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

j)operationTimeout 
这个是可选项,默认1000毫秒,memcached的操作超时时间。 

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

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

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

再说下stick和non-stick的工作流程:

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

Non-Sticky模式:
tomcat session为中转session,memcached1为主session,memcached2为备session。Request请求到来时,从memcached 2加载备session到tomcat,(当容器中还是没有session则从memcached1加载主session到tomcat, 这种情况是只有一个memcached节点,或者有memcached1出错时), Request请求结束时,将tomcat session更新至主memcached1和备memcached2,并且清除tomcat session 。以达到主备同步之目的。 多台tomcat集群时 需要选择Non-Sticky模式,即sticky="false" 

需要清除的是:

1)如果部署后,发现调试不成功,即session不共享,一般都是由于memcached-session-manager-1.7.0.jar、memcached-session-manager-tc7-1.7.0.jar和msm-kryo-serializer-1.7.0.jar这三个jar包出问题。所以版本也很重要。 
2)服务器之间的时间戳一致也非常重要,因为时间不一致将直接导致session过期。 

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏北京马哥教育

系统启动及内核管理

一、知识整理 1、nohup 命令: 不挂断地运行命令,远程连接当断网或断开shell时还可以继续运行。无论是否将nohup命令的输出重定向到终端,输出都将附加...

39780
来自专栏农夫安全

精通脚本黑客txt版-第一章

第1章 本地服务器的搭建 1.1 WEB服务器简介 1.2 ASP服务器的搭建 1...

55860
来自专栏idba

supervisor 工具介绍

supervisor 是一款基于Python的进程管理工具,可以很方便的管理服务器上部署的应用程序。supervisor的功能如下:

11210
来自专栏Sorrower的专栏

详尽! Win10安装Java8+Tomcat9!

Java也好, Tomcat也好, 都是很实用的啦, 早点掌握还是有必要的. 喜欢记得点赞哦, 有意见或者建议评论区哦, 当然暗中关注我也是可以的.

42330
来自专栏北京马哥教育

Linux运维常见故障排查和处理的33个技巧汇总

作为linux运维,多多少少会碰见这样那样的问题或故障,从中总结经验,查找问题,汇总并分析故障的原因,这是一个Linux运维工程师良好的习惯。每一次技术的突破,...

53760
来自专栏云计算教程系列

如何在CentOS 7安装网页服务器(LNMP)

LNMP是一组可用于为动态网页和Web应用程序提供服务的软件。这是一个描述Linux操作系统的首字母缩略词,带有Nginx Web服务器。后端数据存储在MySQ...

35940
来自专栏技术支持log

新购腾讯云 coreOS 系统服务器一段时间后无法登陆原因

问题现象: 新购coreOS刚开始可以正常远程登陆,但是过一段时间后就无法远程登陆了,如果有留意会发现在登陆系统10分钟左右后窗口会弹出下列信息,通知了系统会...

33210
来自专栏老码农专栏

原 荐 介绍一个 Java 程序运行时版本信

23380
来自专栏xingoo, 一个梦想做发明家的程序员

Redis从单机到集群,一步步教你环境部署以及使用

Redis作为缓存系统来说还是很有价值的,在大数据方向里,也是需要有缓存系统的。一般可以考虑tachyon或者redis,由于redis安装以及使用更简单,所...

57460
来自专栏电光石火

Linux重启命令与如何重启网络?

1、shutdown 2、poweroff 3、init 4、reboot 5、halt *---具体说明--- shutdown re...

35280

扫码关注云+社区

领取腾讯云代金券