当后端的tomcat服务器不足以应付请求,压力过大时,考虑做负载均衡集群,此时就需要考虑会话保持的问题,确保同一客户端请求能够被定向到同一台主机,保持会话的连续性。
会话保持,实现与客户端的绑定,确保用户会话的连续性,其实现主要有:
session sticky :会话粘性,通过一定的方式将一个客户的请求始终定向到确定的主机,两种
source ip:基于源IP进行绑定
cookie:基于cookie进行绑定
session cluster :会话集群,将多台主机做成集群,集群中的主机通过多播方式,将本机的session传递给集群中其他主机,这样,客户端在任意一台主机上都可以保证会话的连续性。
session server :使用会话服务器,集中保存会话信息,一般是高速缓存服务器,如
kv(key-value存储形式):memcached,redis
还有一种 url:基于url进行绑定,对于不同的客户端,会给出一个不同的url,一般是在url后增加区别的参数,后台主机基于url进行绑定,在客户端不支持cookie时,经常使用的方法。
(1)LB tomcat
nginx+tomcat,nginx做前端,将请求负载均衡至后端多个 tomcat节点,不是集群,仅是负载均衡。也可以是Apache+tomcat,apache的httpd做前端,实现负载均衡。基于session sticky做会话保持。
(2)LB tomcat cluster
后端tomcat做成集群,前端只负责负载均衡,不需要考虑会话保持问题,因为后端每个tomcat节点都保持有全部session。
(3)LB tomcat
session server方式实现会话保持,即所有会话信息保存在一台高性能缓存服务器中。这里将学习使用memcached保存会话。
实践:(1)LB Tomcat
Nginx做反代 ,环境:
两台tomca主机:192.168.61.128、192.168.61.129
一台nginx主机:192.168.61.130
128上部署一个Host为web2的主机,129上部署一个Host为web1的主机

将Engine的默认主机改为上面定义的主机:

130上部署Nginx,配置反向代理:
在http段中配置upstream和server:

对于以jsp或do结尾的文件,反向代理至tomcat,并且实现负载均衡
访问192.168.61.130:

访问192.168.61.130/index.jsp时,会出现负载均衡的现象,不停刷新页面,交替的在两个tomcat服务器切换。
做会话绑定,只需要将upstream中增加ip_hash:

此时,访问再刷新时,就会固定到同一个tomcat服务器。
Apache httpd做反代同时实现负载均衡 :有三种方案
1)apache:mod_proxy + mod_proxy_http + mod_proxy_balancer (httpd需加载的模块)
tomcat:http connector
2)apache:mod_proxy + mod_proxy_ajp + mod_proxy_balancer(httpd需加载的模块)
tomcat:ajp connector
3)apache:mod_jk (这个模块需要自己下载源代码编译)
tomcat:ajp connector
实践 1)方案:
配置httpd,添加负载集群,增加一个虚拟主机:

route指的是后端tomcat服务器自己的jvm虚拟机的特有标识,在Engine标签中由jvmRoute定义。

修改inidex.jsp:
javascript
<%@ page language="java" %>
<%@ page import="java.util.*" %>
<html>
<head>
<title>Web1</title>
</head>
<body>
<h1><font color="red">Web1.test.com</font></h1>
<table align="centre" border="1">
<tr>
<td>Session ID</td>
<% session.setAttribute("ZZZ.test","zzz.test"); %>
<td><%= session.getId() %></td>
</tr>
<tr>
<td>Create on</td>
<td><%= session.getCreationTime() %></td>
</tr>
</table>
</body>
</html>
显示session ID及其创建时间。
测试访问:

不停地刷新,页面在web1和web2之间切换,session ID每次都变化。
apache httpd上的指令说明 :
ProxyPreserveHost {On|Off}:On启用此功能,代理会将用户请求报文中的Host:行发送给后端的服务器,而不再使用ProxyPass指定的服务器地址。如果想在反向代理中支持虚拟主机,则需要开启此项,否则就无需打开此功能。
ProxyVia {On|Off|Full|Block}:用于控制在http首部是否使用Via:,主要用于在多级代理中控制代理请求的流向。默认为Off,不启用此功能;On表示每个请求和响应报文均添加Via:;Full表示每个Via:行都会添加当前apache服务器的版本号信息;Block表示每个代理请求报文中的Via:都会被移除,包括原生请求报文中的Via。
ProxyRequests {On|Off}:是否开启apache正向代理的功能,httpd具有正向和反向代理功能,但不能同时实现;启用此项时为了代理http协议必须启用mod_proxy_http模块。同时,如果为apache设置了ProxyPass,则必须将ProxyRequests设置为Off,即启用反向代理功能。
ProxyPass [path] !|url [key=value key=value ...]]:将后端服务器某URL与当前服务器的某虚拟路径关联起来作为提供服务的路径,path为当前服务器上的某虚拟路径,url为后端服务器上某URL路径。使用此指令时必须将ProxyRequests的值设置为Off。需要注意的是,如果path以"/"结尾,则对应的url也必须以"/"结尾,反之亦然。也就是反向代理的设置,path是客户端请求的路径,url是反向代理服务器向后端服务器发送请求的路径。
另外,mod_proxy模块在httpd 2.1的版本之后支持与后端服务器的连接池功能,连接按需创建,可以保存至连接池中以备进一步使用。连接池大小或其它设定可以通过在ProxyPass中使用key=value的方式定义。常用的key如下:
- min:连接池的最小容量,此值与实际连接个数无关,仅表示连接池最小要初始化的空间大小。
- max:连接池的最大容量,每个MPM都有自己独立的容量;其值与MPM本身有关,如Prefork的总是为1,而其它的则取决于ThreadsPerChild指令的值。
- loadfactor:用于负载均衡集群配置中,定义对应后端服务器的权重,取值范围为1-100。
- retry:当apache将请求发送至后端服务器得到错误响应时等待多长时间以后再重试。单位是秒钟。
MPM:多路处理模块,负责绑定本机网络端口、接受请求,并调度子进程来处理请求的处理模块。
多路处理模块(Multi-Processing Modules,MPM)是Apache HTTP服务器2.0版本引入的核心功能组件,负责绑定网络端口、接收请求并通过调度子进程或线程处理请求。
如果Proxy指定是以balancer://开头,即用于负载均衡集群时,其还可以接受一些特殊的参数,如下所示:
- lbmethod:apache实现负载均衡的调度方法,默认是byrequests,也就是轮询,即基于权重将统计请求个数进行调度,bytraffic则执行基于权重的流量计数调度,bybusyness通过考量每个后端服务器的当前负载进行调度。
- maxattempts:放弃请求之前实现故障转移的次数,默认为1,其最大值不应该大于总的节点数。
- nofailover:取值为On或Off,设置为On时表示后端服务器故障时,用户的session将损坏;因此,在后端服务器不支持session复制时可将其设置为On。
- stickysession:调度器的sticky session的名字,根据web程序语言的不同,其值为JSESSIONID或PHPSESSIONID。
上述指令除了能在banlancer://或ProxyPass中设定之外,也可使用ProxySet指令直接进行设置,如:
<Proxy balancer://hotcluster>
BalancerMember http://web1.test.com:8080 loadfactor=1
BalancerMember http://web2.test.com:8080 loadfactor=2
ProxySet lbmethod=bytraffic
</Proxy>
ProxyPassReverse:用于让apache调整HTTP重定向响应报文中的Location、Content-Location及URI标签所对应的URL,在反向代理环境中必须使用此指令避免重定向报文绕过proxy服务器。
设置绑定会话:

增加两行,然后再次测试访问,这时,页面停留在一个服务器上,并且sessionID不会变化。如此就实现了session sticky,会话粘性。
mod_proxy状态信息的输出:mod_proxy模块有一个内置的管理页面,配置如下
<Location /balancer-manager>
SetHandler balancer-manager
Proxypass !
Order Deny,Allow
Allow from all
</Location>

实际配置中允许所有人访问,所以配置成Require all granted
访问测试:

实践 2)方案:
只需将配置文件中BalancerMember后的http改为ajp,同时后面的端口由8080改为8009即可。
实践 3)方案:使用mod_jk模块,略。
Session Cluster
session manager:会话管理器,tomcat具有多种会话管理器
标准会话管理器 (StandardManager):
<Manager className="org.apache.catalina.session.StandardManager"
maxInactiveInterval="7200"/>
默认保存于$CATALINA_HOME/work/Catalina/<hostname>/<webapp-name>/下的SESSIONS.ser文件中。
maxActiveSessions:最多允许的活动会话数量,默认为-1,表示不限制;
maxInactiveInterval:非活动的会话超时时长,默认为60s;
pathname:会话文件的保存目录;
持久会话管理器 (PersistentManager):
将会话数据保存至持久存储中,并且能在服务器意外中止后重新启动时重新加载这些会话信息。持久会话管理器支持将会话保存至文件存储(FileStore)或JDBC存储(JDBCStore)中。
保存至文件中的示例:
<Manager className="org.apache.catalina.session.PersistentManager"
saveOnRestart="true">
<Store className="org.apache.catalina.session.FileStore"
directory="/data/tomcat-sessions"/>
</Manager>
每个用户的会话会被保存至directory指定的目录中的文件中,文件名为<session id>.session,并通过后台线程每隔一段时间(checkInterval参数定义,默认为60秒)检查一次超时会话。
保存至JDBCStore中的示例:
<Manager className="org.apache.catalina.session.PersistentManager"
saveOnRestart="true">
<Store className="org.apache.catalina.session.JDBCStore"
driverName="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mydb?user=jb;password=pw"/>
</Manager>
DeltaManager会话管理器:通过多播,将本机的session多播给其他主机,即每台主机都拥有全部的session会话。适用规模较小的场景。
BackupManager会话管理器:分组做集群。
实践:使用tomcat cluster实现session共享
查阅Tomcat文档,最简单的用法是在Engine或Host中添加
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
一般需要具体配置,如下:
javascript
<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.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.ClusterSessionListener"/>
</Cluster>
Manager指定使用哪种会话管理器,这里是Deltamanager;
Channel定义多播通道,其中的Membership指定集群的组播地址、端口等信息,同时也就指定了组播成员;Receiver指定成员如何接收其他节点传递过来的session信息;Sender则指出自己如何把自己的session信息传播出去;
Interceptor,拦截器,用于异常的处理;
1)各节点配置使用DeltaManager;
2)为需要使用session cluster的webapps开启session Distribution的功能:
方法是在WEB-INF/web.xml中增加<distributable/>
针对自己的实际环境进行配置,即修改tomcat配置文件:修改/usr/local/tomcat/conf/server.xml

在具体的webapp中的WEB-INF目录下,修改web.xml,增加<distributable/>
/var/webapps/ROOT/WEB-INF/web.xml:

同样配置128节点主机配置。
启动两台主机,查看日志中的catalina.out:两个成员加入

配置httpd,将会话粘性配置去掉:

此时,httpd负载均衡是轮询,测试访问:

可以看到,session集群实现了。
修改httpd的配置文件,将http协议改为ajp协议,端口改为8009,能够实现同样的效果。
测试Nginx,将nginx的配置中ip hash去掉,即去掉session sticky

同样实现上面的效果。