Tomcat集群环境下session共享方案 通过memcached 方法实现
对于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等进行了指定。
1
2
|
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共享的集群操作记录::
一、集群实验环境信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
主机 端口 开源软件 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
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
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安装
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
|
[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
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
查看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包,如下:
1
2
3
4
5
6
7
8
9
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
这里我的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")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
[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)。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
[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模式的配置如下:
1
2
3
4
5
6
7
8
9
|
<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集群
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
[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集群
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
[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
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
|
[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是否共享
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
[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共享集群的操作记录,下面再追加一些需要注意的东西:
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
上面采用了效率最高的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各参数说明:
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
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的工作流程:
1
2
3
4
5
|
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
2
|
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过期。 |
Tomcat集群环境下session共享方案 通过memcached 方法实现的更多相关文章
- 【SpringBoot】spring-session-data-redis 解决集群环境下session共享
为什么会产生Session共享问题 集群情况下,session保存在各自的服务器的tomcat中,当分发地址至不同服务时,导致sesson取不到,就会产生session共享问题. 解决方案 负载均 ...
- Tomcat 集群中 实现session 共享的三种方法
前两种均需要使用 memcached 或 redis 存储 session ,最后一种使用 terracotta 服务器共享. 建议使用 redis ,不仅仅因为它可以将缓存的内容持久化,还因为它支持 ...
- Nginx+PHP负载均衡集群环境中Session共享方案 - 运维笔记
在网站使用nginx+php做负载均衡情况下,同一个IP访问同一个页面会被分配到不同的服务器上,如果session不同步的话,就会出现很多问题,比如说最常见的登录状态. 下面罗列几种nginx负载均衡 ...
- Tomcat 集群模式下 Session 更新 Bug (redis memcached 及tomcat自已的集群)
从 excel 中导入数据入系统,我们用的是先上传文件至服务器再分析所上传的文件逐行导入. 就是执行了一循环,在当前循环位置标识一下客户端就知道执行的进度了,以前的方式 是用 session.setA ...
- redis 与java的连接 和集群环境下Session管理
redis 的安装与设置开机自启(https://www.cnblogs.com/zhulina-917/p/11746993.html) 第一步: a) 搭建环境 引入 jedis jar包 co ...
- redis内存分配管理与集群环境下Session管理
##################内存管理############### 1.Redis的内存管理 .与memcache不同,没有实现自己的内存池 .在2..4以前,默认使用标准的内存分配函数(li ...
- 在tomcat集群环境下redis实现分布式锁
上篇介绍了redis在集群环境下如何解决session共享的问题.今天来讲一下如何解决分布式锁的问题 什么是分布式锁? 分布式锁就是在多个服务器中,都来争夺某一资源.这时候我们肯定需要一把锁是不是 , ...
- 【原创】Tomcat集群环境下对session进行外部缓存的方法(2)
Session对象的持久化比较麻烦,虽然有序列化,但是并不确定Session对象中保存的其他信息是否可以序列化,这可能是网上很多解决方案摒弃此种做法的原因,网上的很多做法都是将Session中的att ...
- 【原创】Tomcat集群环境下对session进行外部缓存的方法(1)
BJJC网改版, 计划将应用部署在tomcat集群上,集群的部署方案为Apache+Tomcat6,连接件为mod_jk,其中开启了session复制和粘性session.计划节点数为3个. 到这,或 ...
随机推荐
- Python中的PIL
转自:http://blog.csdn.net/yockie/article/details/8498301 介绍 把Python的基础知识学习后,尝试一下如何安装.加载.使用非标准库,选择了图像处理 ...
- 微信小程序 - 仿南湖微科普小程序游戏环节
最近看到南湖微科普小程序游戏环节感觉还可以,于是模仿了下 <view class='current' animation="{{animation}}"> {{curr ...
- AC日记——[SDOI2017]树点涂色 bzoj 4817
4817 思路: 跪烂大佬 代码: #include <bits/stdc++.h> using namespace std; #define maxn 500005 struct Tre ...
- StyleCop setting
StyleCop下载地址:http://stylecop.codeplex.com/ -Documentation Rules 文档化注释规则 -Element Documentaion 变量的文档化 ...
- CF 某套题 O :Grid (简单BFS)
题意: 从左上角跳到右下角最少需要多少步,跳的规则为:可以向四个方向的任意一个方向跳当前格子中的步数,若跳不到右下角输出IMPOSSIBLE. 题解: BFS搜索,注意判断边界,标记. 代码: #in ...
- phpstorm如何进行文件或者文件夹重命名
1.phpstorm的重构 1.1重命名 在phpstorm中,右键点击我们要进行修改的文件,然后又一项重构,我们就可以进行对文件的重命名. 接下来点击重命名进行文件或者文件夹的重新命名. 在框中输入 ...
- 洛谷——P1346 电车
P1346 电车 题目描述 在一个神奇的小镇上有着一个特别的电车网络,它由一些路口和轨道组成,每个路口都连接着若干个轨道,每个轨道都通向一个路口(不排除有的观光轨道转一圈后返回路口的可能).在每个路口 ...
- HZAU 1207 Candies(线段树区间查询 区间修改)
[题目链接]http://acm.hzau.edu.cn/problem.php?id=1207 [题意]给你一个字符串,然后两种操作:1,将区间L,R更新为A或者B,2,询问区间L,R最长的连续的B ...
- 第3天:YAML语法
YAML是一种可读性很强的数据格式语言.正是由于YAML良好的可读性,其广泛引用于软件配置中. 语法规则 YAML文件中的第一行为"---",表示这是一个YAML文件: YAML中 ...
- java 日期validate
public static boolean isValidDate(String str) { boolean convertSuccess=true; // 指定日期格式为四位年/两位月份/两位日期 ...