应用服务器(Tomcat)的高可用负载均衡架构设计主要服务于服务无状态这一特性(静态页面),但是事实上,绝大多数生产业务总是有状态的,例如:交易类的电子商务网站,需要有购物车记录用户的购买信息,每次购买请求都是向购物车中增加新的商品;社交网站中,需要记录用户当前的登录状态,获取用户的个人信息、最新发布的消息及好友请求状态等,用户每次刷新页面都需要更新这些信息。
Session是由应用服务器维持的一个服务器端的存储空间(内存中),用户在连接服务器时,会由服务器生成一个唯一的SessionID,客户端使用该Session ID 为标识符来存取服务器端的Session存储空间。而Session ID则保存到客户端,使用浏览器Cookie保存的,用户提交页面请求时,也会将 Session ID提交到服务器端,来存取Session空间的数据。服务器也通过URL重写的方式来传递Session ID的值,因此不是完全依赖Cookie。如果客户端Cookie禁用,则服务器可以自动通过重写URL的方式来保存Session的值,并且这个过程对程序员透明。
WEB应用中将这些多次请求修改使用的上下文对象称作会话(Session),单机情况下,Session可由部署在服务器上的WEB容器(Tomcat、Resin)进行管理。但在使用高可用负载均衡的集群环境中,由于负载均衡服务器可能会将每次请求分发到集群任何一台应用服务器(Tomcat)上,所以保证每次请求依然能够获得正确的Session比在单机上实现要复杂的多。
ip地址 | 主机名 | 软件包列表 |
---|---|---|
192.168.6.10 | nginx | nginx |
192.168.6.11 | node1 | jdk tomcat |
192.168.6.12 | node2 | jdk tomcat |
安装前准备配置
[root@localhost ~] cat /etc/hosts
192.168.6.10 nginx
192.168.6.11 node1
192.168.6.12 node2
[root@localhost ~] iptables -F
[root@localhost ~] systemctl stop firewalld
[root@localhost ~] setenforce 0
nginx服务器配置
[root@localhost ~] hostname nginx
[root@localhost ~] bash
[root@nginx ~] yum -y install pcre-devel zlib-devel openssl-devel
[root@nginx ~] useradd -s /sbin/nologin nginx
[root@nginx ~] tar xf nginx-1.6.2.tar.gz
[root@nginx ~] cd nginx-1.6.2
[root@nginx nginx-1.6.2] ./configure --prefix=/usr/local/nginx --user=nginx --group=nginx --with-file-aio --with-http_stub_status_module --with-http_ssl_module --with-http_flv_module --with-http_gzip_static_module && make && make install
nginx配置负载均衡
[root@nginx ~] cd /usr/local/nginx/conf/nginx.conf
upstream tomcat_pool {
server 192.168.6.11:8080;
server 192.168.6.12:8080;
}
server {
listen 80;
server_name 192.168.200.101:80;
location / {
proxy_pass http://tomcat_pool;
proxy_set_header X-Real-IP $remote_addr;
}
两台tomcat配置
[root@localhost ~] hostname node1 另外一台机器配置为node2
[root@localhost ~] bash
[root@node1 ~] rpm -qa | grep -i openjdk
java-1.8.0-openjdk-1.8.0.181-7.b13.el7.x86_64
java-1.8.0-openjdk-headless-1.8.0.181-7.b13.el7.x86_64
[root@node1 ~] rpm -e java-1.8.0-openjdk --nodeps
[root@node1 ~] rpm -e java-1.8.0-openjdk-headless
[root@node1 ~] tar xf jdk-8u91-linux-x64.tar.gz
[root@node1 ~] mv jdk1.8.0_91/ /usr/local/java
[root@node1 ~] vim /etc/profile //最后一行添加
export JAVA_HOME=/usr/local/java #设置java根目录
export PATH=$PATH:$JAVA_HOME/bin #在path环境变量中添加java根目录的bin子目录
[root@node1 ~] source /etc/profile
[root@node1 ~] java -version
java version "1.8.0_91"
Java(TM) SE Runtime Environment (build 1.8.0_91-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.91-b14, mixed mode)
[root@node1 ~] tar xf apache-tomcat-7.0.54.tar.gz
[root@node1 ~] mv apache-tomcat-7.0.54 /usr/local/tomcat7
[root@node1 ~] /usr/local/tomcat7/startup.sh
编辑测试页面
[root@node1 ~] vim /usr/local/tomcat7/webapps/ROOT/session.jsp
Session ID:<%= session.getId() %><BR>
SessionPort:<%= request.getServerPort() %>
<% out.println("This tomcat server 192.168.6.11");%>
[root@node2 ~] vim /usr/local/tomcat7/webapps/ROOT/session.jsp
Session ID:<%= session.getId() %><BR>
SessionPort:<%= request.getServerPort() %>
<% out.println("This tomcat server 192.168.6.12");%>
Session绑定可以利用负载均衡的源地址Hash (ip hash) 算法实现。负载均衡服务器总是将来源于同一个IP的请求分发到同一台服务器上,也可以根据Cookie信息将同一个用户的请求总是分发到同一台服务器上。当然这时负载均衡服务器必须工作在HTTP 协议层上。这样整个会话期间,用户所有的请求都在同一-台服务器上处理,即Session绑定在某台特定服务器上,保证Session总能在这台服务器上获取。这种方法又被称为会话黏滞。
但是session绑定的方案显然不符合我们对系统高可用的需求,因为一旦某台服务器宕机,那么该机器上的session也就不复存在了,用户请求切换到其他机器后因为没有session而无法完成业务处理,因此大部分负载均衡服务器都能提供源地址负载均衡算法,但很少用网站利用这个算法。
[root@nginx~] vim /usr/local/nginx/conf/nginx.conf
upstream tomcat_pool {
ip_hash;
server 192.168.6.11:8080 weight=1 max_fails=1 fail_timeout=10s;
server 192.168.6.12:8080 weight=1 max_fails=1 fail_timeout=10s;
}
[root@nginx~] killall -HUP nginx
Session复制是小型架构使用较多的一种服务器集群Session管理机制。应用服务器开启Web容器的Session复制功能,在集群中的几台服务器之间同步Session对象,使每台服务器上都保存了所有用户的Session信息,这样任何一台机器宕机都不会导致Session数据的丢失,而服务器使用Session时,也只需要在本机获取即可。
这种方案实现简单,从本机读取Session信息也很快速,但只能应用在集群规模比较小的环境下。当集群规模较大时,集群服务器间需要大量的通信进行Session复制,占用服务器和网络的大量资源,系统不堪负担。而且由于所有用户的Session信息在每台服务器上都有备份,在大量用户访问的情况下,甚至会出现服务器内存不够Session使用的情况。
而大型网站的核心应用集群就是数千台服务器,同时在线用户可达千万,因此并不适用这种方案。
修改tomcat配置文件(两台都要配置)
[root@node1 tomcat] vim /usr/local/tomcat/conf/server.xml
将Engine这一行修改为:
<Engine name="Catalina" defaultHost="localhost">
<Engine name="Catalina" defaultHost="localhost" jvmRoute="node1"> #tomcat2 配置为jvmRoute="node2"
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/> #去掉注释
[root@node1 ~] vim /usr/local/tomcat/webapps/ROOT/WEB-INF/web.xml
<distributable/> #添加内容
</web-app>
[root@node1 ~] /usr/local/tomcat7/bin/shutdown.sh
[root@node1 ~] /usr/local/tomcat7/bin/startup.sh
[root@node1 ~] netstat -anpt | grep -E ":(8080|4000)"
tcp 0 0 ::ffff:192.168.200.102:4000 :::* LISTEN 2149/java tcp 0 0 :::8080 :::* LISTEN 2149/java
[root@node1 ~] route add -net 224.0.0.0 netmask 240.0.0.0 dev ens32
[root@node2 ~] route add -net 224.0.0.0 netmask 240.0.0.0 dev ens32
两台tomcat上安装memcached
[root@node1 ~] yum -y install libevent memcached
[root@node1 ~] memcached -u root -m 512M -n 10 -f 2 -d -vvv -c 512
选项
-h #查看帮助信息
-p #指定memcached监听的端口号默认11211
-l #memcached服务器的ip地址
-u #memcached程序运行时使用的用户身份必须是root用户
-m #指定使用本机的多少物理内存存数据默认64M
-c #memcached服务的最大连接数
-vvv #显示详细信息
-n #chunk size 的最小空间是多少单位字节
-f #chunk size 大小正的倍数 默认1.25倍
-d #在后台运行
查看是否启动(过滤11211端口)
[root@node1 ~] netstat -anpt |grep 11211
tcp 0 0 0.0.0.0:11211 0.0.0.0:* LISTEN 17854/memcached
tcp6 0 0 :::11211 :::* LISTEN 17854/memcached
测试memcached能否存取数据
[root@node1 ~] yum -y install telnet
[root@node1 ~] telnet 192.168.6.12 11211
Trying 192.168.6.12...
Connected to 192.168.6.12.
Escape character is '^]'.
set username 0 0 8 #指定测试
zhangsan #输入测试内容
STORED
get username #获取测试内容
VALUE username 0 8
zhangsan #得到结果
END
quit #退出
Connection closed by foreign host.
最后执行让 tomcat1 tomcat2 通过(msm)连接到Memcached
上传Tomcat7-Memcached_JAR包
将session包中的“*.jar复制到/usr/local/tomcat/lib/”
[root@node1 ~] mkdir session
[root@node1 ~] cd session/
[root@node1 session] rz
[root@node1 session] ls
javolution-5.5.1.jar minlog-1.2.jar
kryo-1.03.jar msm-javolution-serializer-1.5.1.jar
kryo-serializers-0.10.jar msm-kryo-serializer-1.6.4.jar
memcached-2.5.jar reflectasm-0.9.jar
memcached-session-manager-1.5.1.jar spymemcached-2.7.3.jar
memcached-session-manager-tc7-1.5.1.jar
[root@node1 ~] cp session/* /usr/local/tomcat7/lib/
编辑tomcat配置文件连接指定的memcached服务器,两台tomcat配置一样
[root@node1 ~] vim /usr/local/tomcat7/conf/context.xml (倒数第二行添加)
<Context>
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager" memcachedNodes="memA:192.168.6.11:11211 memB:192.168.6.12:11211" requestUrilgnorePattern=".*\(ico|png|gif|jpg|css|js)$" transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"/>
</Context>
如果成功,tomcat与Memcached端口会连在一起,前后有变化
tomcat1与tomcat2
[root@node1 ~] netstat -anpt | grep java
tcp6 0 0 :::8009 :::* LISTEN 18353/java
tcp6 0 0 :::8080 :::* LISTEN 18353/java
tcp6 0 0 192.168.6.11:33196 192.168.6.11:11211 ESTABLISHED 18353/java
tcp6 0 0 192.168.6.11:45260 192.168.6.12:11211 ESTABLISHED 18353/java
tcp6 0 0 192.168.6.11:33192 192.168.6.11:11211 ESTABLISHED 18353/java
tcp6 0 0 192.168.6.11:45264 192.168.6.12:11211 ESTABLISHED 18353/java
验证
访问192.168.6.10 session id 不变 tomcat服务器变
redis与memcached的区别
将之前从session中复制到/usr/local/tomcat/lib中的文件删除
[root@localhost ~] ls session/ | while read line;do rm -rf /usr/local/tomcat7/lib/$line;done
安装部署redis
[root@localhost ~] tar xf redis-4.0.6.tar.gz -C /usr/src/
[root@localhost ~] cd /usr/src/redis-4.0.6
[root@localhost redis-4.0.6] make
如果安装出现问题报错测需要安装tcl
[root@localhost ~] wget http://downloads.sourceforge.net/tcl/tcl8.5.9-src.tar.gz
[root@localhost ~] cd /tcl8.5.9-src/unix
[[email protected] ~] ./configure && make && make install
接着make就没报错了
配置相关文件
[root@node1 ~] mkdir -p /usr/local/redis/{bin,etc,var}
[root@node1 ~] cd /usr/src/redis-4.0.6/src
src目录下这些文件的作用
edis-server: Redis服务器的daemon启动程序
redis-cli: Redis命令行操作工具你也可以用telnet根据其纯文本协议来操作
redis-benchmark: Redis 性能测试工具,测试Redis在你的系统及你的配置下的读写性能
redis-stat: Redis 状态检测工具,可以检测Redis当前状态参数及延迟状况
[root@node1 src] cp redis-benchmark redis-check-aof redis-cli redis-server /usr/local/redis/bin/
[root@node1 src] cp ../redis.conf /usr/local/redis/etc/
[root@node1 src] vim /usr/local/redis/etc/redis.conf //修改配置文件
bind 127.0.0.0改为bind0.0.0.0 #改成监听到本机的任意IP
daemonize no改为daemonize yes #以进程的方式启动
关闭redis
[root@node1 ~] kill -9 redis-server
开启redis
[root@node1 ~] /usr/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf
查看是否启动
[root@node1 ~] netstat -anpt | grep redis
tcp 0 0 0.0.0.0:6379 0.0.0.0:* LISTEN 21064/redis-server
监控redis共享session:
[root@node1 ~] /usr/local/redis/bin/redis-cli -p 6379 monitor
OK
将tomcat需要调用redis的jar包放入tomcat/lib
[root@node1 ~] mkdir redis
[root@node1 ~] cd redis
[root@node1 redis] rz
[root@node1 redis] cp tomcat-redis-session-manage-tomcat7.jar tomcat-juli.jar commons-logging-1.1.3.jar commons-pool2-2.2.jar jedis-2.5.2.jar /usr/local/tomcat7/lib/
修改tomcat相关文件
[root@node1 redis-3.2.5] vim /usr/local/tomcat7/conf/context.xml
在Context段中加入以下内容
<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" /> <Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager"
host="192.168.6.11" //redis的ip地址 (注意node2也写node1的ip)
port="6379" //redis的端口
database="0"
maxInactiveInterval="60" />
[root@node1 ~] /usr/local/tomcat7/bin/shutudown.sh
[root@node1 ~] /usr/local/tomcat7/bin/startup.sh
验证
192.168.6.10 sessionid不变 ip改变