Untitled

  HTTP事务是无状态的,每条请求/响应都是独立进行的,很多WEB站点希望站点与用户的交互过程中能够识别、跟踪用户。通过cookie和session能够实现此功能。使用session跟踪用户在于将用户的session资料保存在服务器上,并将识别用户的 SessionID 存于响应报文首部 cookie 字段中发给客户端 (Cookie
JSESSIONID=FF289A3D62F1BB1A809E127A39292377-n2
),客户端下次访问服务器将在 cookie 中携带 SessionID,如此便能够识别、跟踪用户。但服务器若不止一台,比如处于集群中的服务器,用户的 session 资料保存于集群中的哪一台服务器上,怎样才能保证相同的用户调度到保存了该用户session资料的服务器上,下文将给出解决方案。


session sticky

使用 session sticky 保持会话,选择前端的调度器的合适调度算法,比如HAProxy 的 source,Nginx的ip_hash等,可以将同一会话的客户端调往同一台后端主机上,从而实现会话保持的目的

Alt text

方法一:httpd (http) + tomcat cluster

  1. LB (172.18.17.10) 安装 httpd
yum -y install httpd
  1. TomcatA (172.18.17.20) 和 TomcatB (172.18.17.21) 安装 tomcat
yum -y install java-1.8.0-openjdk
yum -y install tomcat tomcat-webapps tomcat-lib tomcat-docs-webapp tomcat-admin-webapps
  1. 配置 TomcatA 和 TomcatB
# 创建目录
mkdir /app/webapps/test/WEB-INF -p
# 创建测试JSP程序
[20@root ~]# cat /app/webapps/test/index.jsp
<%@ page language="java" %>
<html>
<head><title>TomcatA</title></head> #TomcatB上为 TomcatB
<body>
<h1><font color="red">TomcatA</font></h1> #TomcatB上为 TomcatB
<table align="centre" border="1">
<tr>
<td>Session ID</td>
<% session.setAttribute("magedu.com","magedu.com"); %>
<td><%= session.getId() %></td>
</tr>
<tr>
<td>Created on</td>
<td><%= session.getCreationTime() %></td>
</tr>
</table>
</body>
</html>
# 修改tomcat配置文件server.xml
vim /etc/tomcat/server.xml
...
<Host name="www.zhubiaook.com" appBase="/app/webapps" unpackWARs="true" autoDeploy="true">
<Context path="" docBase="/app/webapps/test" reloadable="" />
</Host>
...
  1. 配置LB
[23@root conf.d]# cat /etc/httpd/conf.d/tomcat-httpd.conf
Header add Set-Cookie "ROUTEID=.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED
<Proxy balancer://tomcat_servers>
BalancerMember http://172.18.17.20:8080 route=tomcatB loadfactor=1
BalancerMember http://172.18.17.21:8080 route=tomcatA loadfactor=1
ProxySet lbmethod=byrequests
ProxySet stickysession=ROUTEID
</Proxy>
<VirtualHost *:80>
ServerName www.zhubiaook.com
ProxyRequests Off
ProxyPreserveHost On
ProxyVia On
<Proxy *>
Require all granted
</Proxy>
ProxyPass / balancer://tomcat_servers
<Location />
Require all granted
</Location>
# 开启管理端口
<Location /balancer-manager>
SetHandler balancer-manager
ProxyPass !
Require all granted
</Location>
</VirtualHost>
  1. 浏览器上测试


方法二:httpd (ajp) + tomcat cluster

  1. LB主机上httpd配置文件修改为如下即可
[23@root conf.d]# cat tomcat-httpd.conf
Header add Set-Cookie "ROUTEID=.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED
<Proxy balancer://tomcat_servers>
BalancerMember ajp://172.18.17.20:8009 route=tomcatA loadfactor=1
BalancerMember ajp://172.18.17.21:8009 route=tomcatB loadfactor=1
ProxySet lbmethod=byrequests
ProxySet stickysession=ROUTEID
</Proxy>
<VirtualHost *:80>
ServerName www.zhubiaook.com
ProxyRequests Off
ProxyPreserveHost On
ProxyVia On
<Proxy *>
Require all granted
</Proxy>
ProxyPass / balancer://tomcat_servers
<Location />
Require all granted
</Location>
# 开启管理端口
<Location /balancer-manager>
SetHandler balancer-manager
ProxyPass !
Require all granted
</Location>
</VirtualHost>


方法三:nginx + tomcat cluster

LB主机上安装nignx,并修改配置文件

[23@root conf.d]# cat /etc/nginx/conf.d/tomcat-nginx.conf
upstream tomcat_servers {
ip_hash;
server 172.18.17.20:8080;
server 172.18.17.21:8080;
}
server {
listen 80;
server_name www.zhubiaook.com;
location / {
proxy_pass http://tomcat_servers;
proxy_set_header Host $host;
}
}


session cluster

使用 session cluster 保持会话,Tomcat主机建立集群,集群主机间使用组播互相同步各自所拥有的会话资料,从而保证集群主机间每台主机上都拥有整个集群的所有会话资料,无论客户端调度到哪一台主机上都不拥有以前所保存的会话信息,从而实现会话保持。

  1. 同步 TomcatA 和 TomcatB 的时间
ntpdate 172.18.0.1
  1. 编辑/app/webapps/test/WEB-INF/web.xml,添加<distributable/>元素,web.xml 可从/etc/tomcat/web.xml处复制
cp /etc/tomcat/web.xml /app/webapps/test/WEB-INF/
  1. 确保在<Engine>中配置 jvmRoute
<Engine name="Catalina" defaultHost="localhost" jvmRoute="TomcatA"> <!-- 另一个Tomcat上为 jvmRoute="TomcatB" -->
  1. 配置启用集群,将下列配置放置于<engine><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" <!-- 两台tomcat相同的组播地址-->
port="45564"
frequency="500"
dropTime="3000"/>
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="172.18.17.20" <!- 另外一台为172.18.17.21 -->
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.MessageDispatch15Interceptor"/>
</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.JvmRouteSessionIDBinderListener"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
  1. 测试

Alt text


session server

以上两种保持会话的方法各有缺陷,使用 session sticky 保持会话,会影响负载均衡。当后端主机宕机,则丢失会话信息。而使用 session cluster 保存会话无法扩展,当后端集群过大,会话资料将占据过多存储,主机直接同步会话资料有延迟等不足。基于以上两点,可以使用第三种方法来保存会话,将会话资料保存到专用的会话服务器上,防止会话服务器单点故障,使用两台或更多的会话服务器做冗余。下面我们将介绍使用Memcached保存会话。


Memcached 介绍

  memcached 是以LiveJournal 旗下Danga Interactive 公司的Brad Fitzpatric 为首开发的一款软件。现在已成为mixi、hatena、Facebook、Vox、LiveJournal等众多服务中提高Web应用扩展性的重要因素。许多Web应用都将数据保存到RDBMS中,应用服务器从中读取数据并在浏览器中显示。但随着数据量的增大、访问的集中,就会出现RDBMS的负担加重、数据库响应恶化、网站显示延迟等重大影响。这时就该memcached大显身手了。memcached是高性能的分布式内存缓存服务器。一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。

  如下图所示,应用服务器首次从RDBMS读取数据,然后以键值对(对于RDMBS,查询语句经过应用服务进行Hash计算存储为键,查询到的数据作为值)的方式将数据保存到Memcached中,下次应用应用服务器访问相同资源时将直接从Memecached服务器中读取。

Alt text

Memecached作为高速运行的分布式缓存服务器,有以下特性

  • 协议简单
  • 基于 libevent 的事件处理
  • 内置内存存储方式
  • memcached 不互相通信的分布式

Memcached也有的缺陷

  • 由于数据均存储于内存中,一旦服务器宕机,所有缓存将全部丢失,对于强依赖缓存的应用将是致命性的缺陷
  • 由于Memcached是旁挂式缓存服务器,不会主动去缓存数据,它所谓的分布式和智能性一半依赖于客户端(调用memcached的API开发程序),一半依赖于服务端
  • 支持缓存的数据类型单一,只支持字节性数据,其它数据类型必须能够序列化方可缓存。


Memcached 做为 session server 实现会话保持

Alt text

  1. MemcA和MmecB 上安装 memecached 并启动
yum -y install memecached
systemctl start memcached
[23@root ~]# netstat -tunlp | grep memcached
tcp 0 0 0.0.0.0:11211 0.0.0.0:* LISTEN 977/memcached
tcp6 0 0 :::11211 :::* LISTEN 977/memcached
udp 0 0 0.0.0.0:11211 0.0.0.0:* 977/memcached
udp6 0 0 :::11211 :::* 977/memcached
  1. 下载如下jar文件
    下载地址及参考文档
spymemcached-2.12.3.jar
reflectasm-1.11.3-shaded.jar
reflectasm-1.11.3.jar
objenesis-2.6.jar
msm-kryo-serializer-2.1.1.jar
minlog-1.3.0.jar
memcached-session-manager-tc7-2.1.1.jar
memcached-session-manager-2.1.1.jar
kryo-serializers-0.42.jar
kryo-4.0.1.jar
asm-5.2.jar
  1. 将以上jar文件放置到 /usr/share/java/tomcat/ 目录下

  2. 分别在两个tomcat上的配置文件/etc/tomcat/server.xml某host上定义一个用于测试的context容器,并在其中创建一个会话管理器,如下所示:

<Host name="www.zhubiaook.com" appBase="/app/webapps" unpackWARs="true" autoDeploy="true">
<Context path="" docBase="/app/webapps/test" reloadable="">
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
memcachedNodes="n1:172.18.17.22:11211,n2:172.18.17.23:11211"
failoverNodes="n1"
requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
/>
</Context>
</Host>
  1. 测试

Alt text