会话保持
理论部分
会话也就是session,主要存储在服务器端,用来识别用户的身份。
在浏览器中向服务端发送请求的时候,不是http协议就是https协议,而两种协议在发送请求的时候,都是基于http协议,http协议又是无状态的,也就是stateless,在每次用户发送请求过去的时候,这都是一次新的连接,服务端是不会认识你是谁的。
那么怎么让服务端来识别客户端谁是谁呢,从而也就有cookie,cookie好像没有中文翻译,cookie主要存储在客户端,一般是保存在某个特定路径的文件之中,从而在每次发送请求的时候,都会附加上这个cookie信息,服务端得到这个信息之后,就会判定你是哪个用户等信息,cookie主要就是找到服务端的session信息,从而用来追踪用户。
在生产环境中,只要涉及到负载均衡的地方,我们就需要考虑会话保持,否则假如一个应用程序是需要登录的,那么会频繁的需要登录。
负载均衡主要分为硬件负载均衡F5,CITRIX等,nginx负载均衡,apache负载均衡,无论是硬件负载均衡或者是软件负载均衡都会有一个会话保持的选项,在需要进行会话保持的时候,在每一个环节都需要进行会话保持,也就是硬件负载均衡要选择会话保持,在软件负载均衡也要进行会话保持。
生产环境使用的过时的(第一种):
在中间件的选择上面,apache和webspare在逐渐淘汰,在使用此种架构的时候,F5会使用会话保持,nginx也使用的是ip_hash进行会话保持。此种架构的最大的优势是部署比较简单,在进行部署的时候只要将java程序的war包放置到具体的目录即可进行部署,在进行自动化发布的时候,进行部署是非常简单的事,因为集群中各个机器是独立的,一个机器出问题,不会影响其他的机器;缺点就是在部署的时候,需要在每个主机上进行放置相同的war包,并且配置要保持一直;在需要会话保持的场景中,如果一台主机down,那么就会导致所有的会话丢失。
生产环境使用的过时的架构(第二种):
在使用domain模式的时候,前端的F5和nginx都不需要进行会话保持,只要做到负载均衡就好了。此种方式的最大有点就是在自动化部署的时候只要在domain节点的master或者是dmgr部署程序即可,主节点会自动将相关的程序同步到其他的slave或者node节点上去,在修改配置的时候,也只要在master上进行修改即可,在会话保持的上面,主机之间会同步相关的会话信息,从而不用担心某台服务器宕机从而导致会话丢失。缺点就是,无论是在发布或者同步配置的时候,主节点总要联系上从节点,进行同步,而从节点的相关进程还是比较容易出现假死的情况,在使用这种集群的时候,只要master节点或者dmgr节点的服务器损坏,那么这个集群就不能重新发布新的程序版本了,但是不会影响服务的可用性,在每次出现问题的时候,因为同步进程有好几个,你都不知道是那个进程假死了,只能全部进行重启一遍,说好的稳定性呢。为啥这种慢慢淘汰,一个是因为在自动化发布的时候,很容易出现问题;在进行灰度发布的时候,standalone模式更容易控制,而且domain模式有主机数量的限制,而standalone模式理论上无上限。
生产环境目前架构如下:
在使用此种架构的时候,前端无须进行会话保持,后端也无须进行会话的同步,在有后端主机宕机的时候,也不会丢失会话,灰度发布也很容易,自动化做起来比较简单,从而也就成为了目前的架构。
使用tomcat standalon演示会话保持
tomcat的首页内容如下所示(可以查看到页面的session信息):
[root@mogilenode3 kel]# cat index.jsp <%@ page language="java" %> <%@ page import="java.util.*" %> <html> <head> <title>JAVA PAGE</title> </head> <body> <% out.println("mogilenode3"); %> <%= session.getId() %> </body> </html>
在使用负载均衡的时候,准备两台主机,均需要安装jdk和tomcat,并且提供相同的应用程序,在首次访问的时候,均会设置cookie信息,在java程序中,其中会有JSESSIONID,在第二次访问同样的主机的时候,就不会再次设置这个cookie信息了。
使用nginx进行反代的时候,nginx的主要配置文件如下所示:
在nginx的时候,主要的就是使用ip_hash。
在使用apache进行反代的时候,可以使用http协议或者ajp协议,配置文件如下所示(在编译的时候,需要几个模块mod_proxy,mod_proxy_http,mod_proxy_ajp,mod_proxy_balancer):
NameVirtualHost *:80 <proxy balancer://tomserver> BalancerMember ajp://192.168.1.236:8009 loadfactor=1 route=mogilenode1 BalancerMember ajp://192.168.1.238:8009 loadfactor=1 route=mogilenode3 ProxySet stickysession=ROUTEID </proxy> <VirtualHost *:80> DocumentRoot "/usr/local/apache/htdocs" ServerName www.kel.com ServerAlias www.kel.com ProxyVia On ProxyRequests Off ProxyPreserveHost On ProxyPass /status ! <Location /balancer-manager> SetHandler balancer-manager ProxyPass ! order allow,deny allow from all </Location> ProxyPass / balancer://tomserver/ ProxyPassReverse / balancer://tomserver/ ErrorLog "logs/www.kel.com-error_log" CustomLog "logs/www.kel.com-access_log" common </VirtualHost> <Location /status> SetHandler server-status Order allow,deny Allow from all </Location> ExtendedStatus On
使用http协议的配置如下:
NameVirtualHost *:80 Header add Set-Cookie "ROUTEID=.%{BALANCER_WORKER_ROUTE}e;path=/" env=BALANCER_ROUTE_CHANGED <proxy balancer://tomserver> BalancerMember http://192.168.1.236:8080 loadfactor=1 route=mogilenode1 BalancerMember http://192.168.1.238:8080 loadfactor=1 route=mogilenode3 ProxySet stickysession=ROUTEID </proxy> <VirtualHost *:80> DocumentRoot "/usr/local/apache/htdocs" ServerName www.kel.com ServerAlias www.kel.com ProxyVia On ProxyRequests Off ProxyPreserveHost On ProxyPass /status ! <Location /balancer-manager> SetHandler balancer-manager ProxyPass ! order allow,deny allow from all </Location> ProxyPass / balancer://tomserver/ ProxyPassReverse / balancer://tomserver/ ErrorLog "logs/www.kel.com-error_log" CustomLog "logs/www.kel.com-access_log" common </VirtualHost> <Location /status> SetHandler server-status Order allow,deny Allow from all </Location> ExtendedStatus On
在次两种的配置下,tomcat需要配置如下信息:
<Engine name="Catalina" defaultHost="www.kel.com" jvmRoute="mogilenode1">
使用tomcat cluster演示会话保持
在apache或者nginx端只要做负载均衡的配置即可,也就是在nginx中去掉ip_hash,在apache中去掉ProxySet stickysession=ROUTEID,主要的配置在tomcat的host标签中,如下所示:
<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="228.0.0.4" port="45564" frequency="500" dropTime="3000"/> <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver" address="auto" port="4000" 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.MessageDispatchInterceptor"/> </Channel> <Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=""/> <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/> <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer" tempDir="/tmp/war-temp/" deployDir="/tmp/war-deploy/" watchDir="/tmp/war-listen/" watchEnabled="false"/> <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/> </Cluster>
在其中需要修改的组播地址,防止部署多个集群的时候,出现地址冲突,在部署的时候,可能出现的错误如下:
java.net.SocketException: No such device; No faulty members identified. SEVERE: Unable to start cluster.
主要是因为没有设置网关地址或者组播地址,在其中如下:
[root@mogilenode2 extra]# route add default gw 192.168.1.1 [root@mogilenode2 extra]# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 192.168.1.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 192.168.249.0 0.0.0.0 255.255.255.0 U 0 0 0 eth1 169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth0 169.254.0.0 0.0.0.0 255.255.0.0 U 1003 0 0 eth1 0.0.0.0 192.168.1.1 0.0.0.0 UG 0 0 0 eth0
或者使用如下方法:
[root@mogilenode1 kel]# route add -net 224.0.0.0 netmask 240.0.0.0 dev eth0 [root@mogilenode1 kel]# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 192.168.1.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 192.168.249.0 0.0.0.0 255.255.255.0 U 0 0 0 eth1 169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth0 169.254.0.0 0.0.0.0 255.255.0.0 U 1003 0 0 eth1 224.0.0.0 0.0.0.0 240.0.0.0 U 0 0 0 eth0
在应用程序的web.xml中,在web-app标签中添加指令:
<distributable/>
监听的地址如下:
[root@mogilenode3 WEB-INF]# netstat -tnlp|grep java tcp 0 0 :::8080 :::* LISTEN 3920/java tcp 0 0 ::ffff:192.168.1.238:4000 :::* LISTEN 3920/java tcp 0 0 ::ffff:127.0.0.1:8005 :::* LISTEN 3920/java tcp 0 0 :::8009 :::* LISTEN 3920/java
测试
在第一次发送请求的时候,会设置cookie信息,如下所示:
在第二次发送请求的时候,会带上Cookie信息,如下:
在JAVA程序中,只要收到了jsp或者servlet的请求,那么就会生成JSESSIONID。