Apache + Tomcat 负载均衡 session复制
Posted gavanwanggw
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Apache + Tomcat 负载均衡 session复制相关的知识,希望对你有一定的参考价值。
转自:http://blog.csdn.net/cssmhyl/article/details/8455400
http://snowolf.iteye.com/blog/743611
Apache 和 Tomcat原本就是一家,更是一家亲!
Apache与Tomcat整合,无非是将Apache作为前端依据请求路径、端口、代理分发给多个Tomcat,以到达转发和负载均衡的目的!同一时候。通过Apache和Tomcat相互作用,进行粘性会话,会话拷贝构建集群!这一切的最终结果就是“云服务”。不要说Session不重要,当下火爆的团购,假设离开Session还能快活多久?怎样保证Session同步。仍然是不能回避的问题!
这里要说的是基于HTTP和AJP跳转方式的负载均衡实现,关于JK,因为效率问题一直成为诟病,而且mod_jk2模块已经不再被更新了,这里就不折腾它的复杂配置了!
至于说Apache+Tomcat+SSL。并非难题!
仅仅要完毕了Apache+SSL然后配置相应的负载均衡、反向代理等等就能够达到目的,相关Apache+SSL參考征服 Apache + SSL
Ubuntu Server 10.04版本号,Apache选用2.2.14,Tomcat选用6.0.24。
相关内容:
征服 Apache + SSL
征服 Apache + SVN
征服 Apache + SVN + LDAP
征服 Apache + Tomcat
征服 Nginx
征服 Nginx + Tomcat
步骤:
- 安装Apache基本模块
- 后台监控
- 负载均衡简单測试
- 配置Tomcat相关模块(AJP)
- 保持Session唯一。粘性会话
- Tomcat集群,Session复制
1.安装Apache相关模块
负载均衡须要的主要是代理模块。
经过几次Apache配置尝试,在Ubuntu下配置Apache实在是太easy了。载入什么模块、取消什么模块两个命令搞定。
- #启用模块
- sudo a2enmod <model>
- #禁用模块
- sudo a2dismod <model>
这里,我们须要让Apache提供代理服务。当中又包括基于http、ftp、ajp等等协议的代理功能,同一时候还须要负载均衡模块。
我们能够通过命令逐个载入:
- #代理核心模块
- sudo a2enmod proxy
- #代理AJP模块
- sudo a2enmod proxy_ajp
- #代理负载均衡模块
- sudo a2enmod proxy_balancer
- #代理HTTP模块
- sudo a2enmod proxy_http
- #代理FTP模块
- sudo a2enmod proxy_ftp
完毕上述操作后,系统会提示重新启动Apache!
先不着急重新启动,现学现卖,了解下Apache的文件夹结构。在Ubuntu下配置Apache主要是在/etc/apache2文件夹下:
分述:
- apache2.conf核心配置文件,一般不须要改动!
- conf.d文件夹,里面包括了一些字符集设置,文档等设置。
- dav_svn.authz和dav_svn.passwd是前面做SVN时。相关权限、password文件。
- envvars定义了执行时的用户身份——www-data。
- httpd.conf是Apache留给我们自己折腾的配置文件,默觉得空。apache2.conf会载入这个文件。
- ports.conf端口默认配置。
apache2.conf会载入这个文件。
- magic为mod_mime_magic模块服务。
- mods-enabled和mods-available mods-enabled会被apache2.conf载入。里面包括*.load和*.conf文件。*.load文件里是载入相应的模块(位于/usr/lib/apache2/modules/中),而*.conf中是相应的基本配置。但这些文件事实上都是链接到mods-available中相应的文件上。
当我们通过a2enmod操作时。实际上正是操作了这些软链接。
- sites-available和sites-enabled 与 mods-enabled和mods-available的关系相似,仅仅是当中包括的是网站内容。
罗嗦了一堆,以下配置负载均衡部分。
执行改动:
- sudo vi /etc/apache2/mods-available/proxy.conf
上图红框中的内容是原始内容,白框中的内容是我新加的部分。
注意红框中的配置:
- <Proxy *>
- AddDefaultCharset off
- Order deny,allow
- #Deny from all
- Allow from localhost ip6-localhost
- </Proxy>
在默认配置中,Deny from all处于可用状态。当我们配置其它代理节点时,将导致杜绝訪问。使用Allow from localhost ip6-localhost 限制仅同意本机訪问。
再说,白框中的内容:
- <Proxy balancer://zlex>
- BalancerMember http://localhost:8080/
- BalancerMember http://192.168.49.1:8080/
- </Proxy>
这里,我配置了一个负载均衡节点balancer://zlex。当中包括了两个服务http://localhost:8080/和http://192.168.49.1:8080/。一个是虚拟机上的Tomcat、一个是真机上的Tomcat。这里使用的Http的转发方式,当然,使用AJP未尝不可。稍后详述!
这里的节点次序会有一个先后关系,Apache会将请求依照FIFO的方式调度顺次分配到各个节点上。假设当中有一个节点挂掉,将跳过该节点顺次寻找可用节点。
再说代理和反向代理:
- ProxyPass /zlex balancer://zlex
- ProxyPassReverse /zlex balancer://zlex
这里配置,假设要訪问/zlex路径时,将跳转到balancer://zlex上,也就是享受负载均衡!
2.后台监控
我们怎样知道某个节点负载多少,响应时间多久,服务是否正常呢?Apache提供了负载均衡监控平台:http://localhost/balancer-manager。可是。这个服务默认是不存在。
除了用于负载均衡配置、监控的balancer-manager还有http://localhost/server-status和http://localhost/server-info
我们须要加入info模块:
- #系统信息模块
- sudo a2enmod info
同一时候,为了能够使用balancer-manager。我们须要配置:
- <Location /balancer-manager>
- SetHandler balancer-manager
- Order Deny,Allow
- #Deny from all
- Allow from localhost ip6-localhost
- </Location>
把这段代码放到哪?因为它同属系统信息配置,我把它放到了info.conf中,说白了就是照猫画虎:
- sudo vi /etc/apache2/mods-available/info.conf
注意,这段代码放到了<IfModule mod_info.c>和</IfModule>之间!
如今。我们重新启动Apache:
- sudo /etc/init.d/apache2 restart
来看看管理界面http://localhost/balancer-manager:
我们再来看看服务器基本信息http://localhost/server-info:
上述两个服务须要载入info模块。而服务器状态(server-status)不须要http://localhost/server-status:
3.负载均衡简单測试
疯狂訪问http://localhost/zlex。直到手酸眼烦!
我这里有益使用不同了Tomcat界面,来验证自己的配置是否生效。
更疯狂的是,我甚至把节点指向了百度、搜狐。来測试负载均衡的效果。假设你细致观察。Apache是将请求顺次分配到各个节点上的。
假设当中一个节点发生问题(比如。强行关闭一个Tomcat。或配置一个错误节点)Apache将会经过几次尝试后,绕过这个问题节点,寻找能够成功訪问的节点。假设这个节点恢复正常使用,Apache将在该Tomcat恢复正常工作后大约1分钟内将该节点标识为可用!
如今,再看看如今的后台(http://localhost/balancer-manager)啥样子:
假设我们控制一个节点的状态是否可用,该怎么做:
涉及到负载量,session同步等等。我们最后讨论!
4.配置Tomcat相关模块(AJP)
基于Http协议分发并不复杂,但AJP效果更好。一次诡异事件中,内网訪问正常,外网訪问多次失败。最后通过AJP方式完美攻克了!
在Tomcat中配置AJP也非常简单,改动server.xml开启AJP模块:
- sudo vi /etc/tomcat6/server.xml
开启AJP配置:
- <Connector port="8009" protocol="AJP/1.3"
- URIEncoding="UTF-8"
- redirectPort="8443" />
注意使用的端口port="8009",字符集URIEncoding="UTF-8",这是输入框、请求字符集乱码的入口!
接下里就能够通过AJP方式进行节点分发了。改动/etc/apache2/mods-available/proxy.conf :
- sudo vi /etc/apache2/mods-available/proxy.conf
将http改为ajp,将8080改为8009:
- <Proxy balancer://zlex>
- BalancerMember ajp://localhost:8009/
- BalancerMember ajp://192.168.49.1:8009/
- </Proxy>
重新启动Apache:
- sudo /etc/init.d/apache2 restart
再看看管理界面http://localhost/balancer-manager
至此,我们完毕了基本负载均衡的基本配置!
/etc/apache2/mods-available/proxy.conf另一些属性:
noFailOver是否打开失败转移,On|Off,默觉得Off,加入在ProxyPass后面,如:
- ProxyPass /zlex balancer://zlex stickySession=JSESSIONID noFailOver=On
假设这样配置。当提供给你服务的服务器发生异常。那么你将一直看着它返回给你503。直到系统恢复正常!
loadfactor表示后台服务器负载到由Apache发送请求的权值,默认值为1加入在BalancerMember后面:
- <Proxy balancer://zlex>
- BalancerMember ajp://localhost:8009/
- BalancerMember ajp://192.168.49.1:8009/
- </Proxy>
能够实现三种策略:
- 轮询均衡策略的配置
- 按权重分配均衡策略的配置
- 权重请求响应负载均衡策略的配置
5.Session唯一,粘性会话
Apache已经能够轻松将内容处理的工作分配给各个Tomcat了。
当然,这还不够。Session还是个问题!
WHY?
我们来做一系列改动,来检測Session究竟出现了什么问题!
先来改造Tomcat,改动server.xml:
- sudo vi /etc/tomcat6/server.xml
改动<Engine />节点,添加jvmRoute属性:
- <Engine
- name="Catalina"
- defaultHost="localhost"
- jvmRoute="tomcat1">
另一个Tomcat设置改为
- <Engine
- name="Catalina"
- defaultHost="localhost"
- jvmRoute="tomcat2">
通过jvmRoute,指定了Tomcat唯一标识!
然后改动/etc/apache2/mods-available/proxy.conf
- sudo vi /etc/apache2/mods-available/proxy.conf
例如以下:
- <Proxy balancer://zlex>
- BalancerMember ajp://localhost:8009/zlex route=tomcat1
- BalancerMember ajp://192.168.49.1:8009/zlex route=tomcat2
- </Proxy>
- ProxyPass /zlex balancer://zlex
- ProxyPassReverse /zlex balancer://zlex
这里须要通过改动route属性,将Apache与Tomcat关联起来!
注意,Tomcat中定义的jvmRoute须要与Apache定义的route相相应。
我们来看一下http://localhost/balancer-manager发生了什么变化:
我们注意到route字段有了新的标识。当然。我们也能够通过这个配置界面改动这些信息,但当前改动不会真的改动/etc/apache2/mods-available/proxy.conf文件,Apache重新启动后将丢失。
为了更细致的对照进过复杂均衡的结果,这里添加了zlex应用!主要是监控Session的变化!
仅仅看核心代码:
- <b>当前SessionID:</b>
- <br />
- <%
- String sessionID = session.getId();
- out.println(sessionID);
- System.err.println("sessionID = " + sessionID);
- // 假设有新的 Session 属性设置
- String dataName = request.getParameter("dataName");
- if (dataName != null && !dataName.isEmpty()) {
- String dataValue = request.getParameter("dataValue");
- session.setAttribute(dataName, dataValue);
- }
- %>
- <br />
- <br />
- <b>Session属性列表:</b>
- <br />
- <%
- Enumeration<String> e = (Enumeration<String>) session
- .getAttributeNames();
- while (e.hasMoreElements()) {
- String name = e.nextElement();
- String value = (String) session.getAttribute(name);
- out.println(name + " = " + value + "<br>");
- System.err.println(name + " = " + value);
- }
- %>
- <form method="POST">
- <ul style="list-style-type: none;">
- <li><label for="dataName">键:</label><input size="20" id="dataName"
- name="dataName"></li>
- <li><label for="dataValue">值:</label><input size="20"
- id="dataValue" name="dataValue"></li>
- <li><input type="submit" value="提交" /></li>
- </ul>
- </form>
将其做成一个名为zlex的web应用。分别部署到两个Tomcat上!
然后重新启动Apache:
- sudo /etc/init.d/apache2 restart
不断刷新http://localhost/zlex,看看真正的结果:
第1次:
第2次:
第3次:
第4次:
细致观察。每次请求都依照负载均衡配置的节点次序依次请求到不同的Tomcat上。
尤其是当我们通过jvmRoute和route做了绑定之后,信息更加准确。
可是,细致观察,每次请求的SessionID都是不一样!
对于纯Web应用。尤其是依靠SessionID区分唯一用户的应用。这将是一场噩梦——攻克了服务器压力均衡问题,却带来了SessionID不唯一问题!
这就须要SessionID绑定,或者说叫做“会话复制”。
假设这时候你在页面上提交表单,将键值对保持在session中。在页面刷新后。将无法获得该信息。因为Seesion丢失了!
接着改动/etc/apache2/mods-available/proxy.conf。让SeesionID保持唯一:
- sudo vi /etc/apache2/mods-available/proxy.conf
添加stickySession属性:
- ProxyPass /zlex balancer://zlex stickySession=JSESSIONID
stickySession粘性会话,依据这一属性,浏览器将通过cookie绑定SeesionID。假设这个时候再次訪问http://localhost/zlex,你会发现。页面不会来回跳转了。
sticky是什么?
利用负载均衡器的sticky模式的方式把全部同一session的请求都发送到同样的Tomcat节点。
这样不同用户的请求就被平均分配到集群中各个tomcat节点上,实现负载均衡的能力。
这样做的缺点是没有灾难恢复的能力。一旦一个节点发生问题。这个节点上全部的session信息全部丢失;
同一用户同一session仅仅和一个webServer交互。一旦这个webserver发生问题,本次session将丢失,用户不能继续使用 !
提交一个Session设定看看http://localhost/zlex:
观察后台日志:
再看看返回页面,这相当于一次页面刷新。假设正常粘性会话,我们将获得当前SessionID相应的一切信息:
这说明粘性会话生效了!
我们得到了形如
假设。换一个浏览器打开该页面http://localhost/zlex,将会获得一个新的SessionID,而且,依据Apache中配置的负载均衡节点列表依次訪问下一个节点!
假设这时候负载均衡节点列表中某一节点发生异常,那么Apache将依照惯例,跳转该节点,并在该节点恢复正常后约1分钟内又一次将其纳入可用节点!
改动刚才的jsp页面,看看Http头中都有些什么:
- <b>Cookie信息:</b>
- <br />
- ${header["cookie"]}
- <br />
- <b>Host信息:</b>
- <br />
- ${header["host"]}
- <br />
sticky模式的根本在于浏览器支持cookie。假设浏览器不支持cookie,则须要改动server.xml文件里的<Context />节点,将cookie置为false,关闭cookie功能,让jsessionid显式传递!
6.Tomcat集群,Session复制
经过两天重复研究。两仅仅互不相认的Tomcat最终在网络上“资源共享”了——Session复制成功!
关于Tomcat集群以及Session复制。网上已经有非常多非常多,可是否真的能用?。为了确认这一问题,周末还跑到书店翻了翻《Apache Tomcat 高级编程》,參考Clustering/Session Replication HOW-TO(有点小错误)。经过两天苦战,克服种种小问题,最终拿下!
整理概念:
群集。是包括多个服务器实例的指定集合,这些服务器实例共享同样的应用程序、资源以及配置信息。您能够将不同计算机上的服务器实例分组到一个逻辑群集中并将其作为一个单元来管理。您能够使用 DAS 轻松控制多机群集的生命周期。
群集能够实现水平可伸缩性、负载平衡和故障转移保护。依据定义,群集中的全部实例都具有同样的资源和应用程序配置。当群集中的服务器实例或计算机出现问题时,负载平衡器检測到该故障,会将通信从出现问题的实例重定向至群集中的其它实例。并恢复用户会话状态。
因为群集中全部实例上的应用程序和资源都同样,因此一个实例能够故障转移至群集中的不论什么其它实例。
假设当中一台服务器发生问题。依据负载均衡的原理,Apache会遍历寻找可用节点,分发请求。与此同一时候,当前用户Session不能发生数据丢失,其余各节点服务器应保证用户Session数据同步。
Session复制核心内容主要是:
- Session内容序列化(serialize),会消耗系统性能。
- Session内容通过广播同步给成员,会造成网络流量瓶颈,即便是内网瓶颈。
因此。Session复制的这两个潜在问题。致使复杂均衡节点最多不会超过4个。
因为。当节点数大于4时,整个集群的吞吐量将不再上升!
为了搭建Tomcat集群,我将两个Tomcat分别部署到两台虚拟机上,确保网段一致。(这一步非常关键。我最初将Tomcat1(192.168.49.132)部署在虚拟机上,将Tomcat2(192.168.49.128)部署在本机上,结果,网络总有问题,耽误了非常多时间。 )
因为变换了IP。我须要改动Apache的/etc/apache2/mods-available/proxy.conf文件:
- sudo vi /etc/apache2/mods-available/proxy.conf
改动负载均衡节点例如以下:
- <Proxy balancer://zlex>
- BalancerMember ajp://192.168.49.128:8009/zlex route=tomcat1
- BalancerMember ajp://192.168.49.132:8009/zlex route=tomcat2
- </Proxy>
对于windows系统,不须要考虑网络问题,广播地址(这里用到224.0.0.0和240.0.0.0)默认开放,对于linux则须要通过命令开放地址。
Ubuntu上开放广播地址(eth0网卡):
- sudo route add -net 224.0.0.0 netmask 240.0.0.0 dev eth0
然后通过-v參数查看当前开放的广播地址:
- route -v
注意,重新启动后。该路由设置将丢失。
在Ubuntu下。能够考虑改动/etc/networks文件!
假设有必要。Windows上开放广播地址(192.168.49.128本机地址):
- route add 224.0.0.0 mask 240.0.0.0 192.168.49.128
然后通过print參数查看当前开放的广播地址:
- route print
然后,改动tomcat的server.xml文件:
- sudo vi /etc/tomcat6/server.xml
在<Engine /> 节点中加入例如以下内容:
- <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="224.0.0.0"
- port="45564"
- frequency="500"
- dropTime="3000"/>
- <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
- address="192.168.49.1"
- 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=".*\.gif;.*\.js;.*\.jpg;.*\.png;.*\.htm;.*\.html;.*\.css;.*\.txt;"/>
- <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
- <ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/>
- <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
- </Cluster>
这里须要注意<Membership />和Receiver:<Membership />节点的address属性是广播地址。Receiver节点的address属性是本地绑定地址。当然。默觉得auto。
因为我在启动Tomcat时,Tomcat频频将地址指向127.0.0.1。无奈仅仅好使用固定IP。
此外,为了减少Session复制的成本,Tomcat通过<Valve />节点。以过滤器的方式控制哪些请求能够忽略Session复制:
- <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
- filter=".*\.gif;.*\.js;.*\.jpg;.*\.png;.*\.htm;.*\.html;.*\.css;.*\.txt;"/>
同一时候。在<Host>节点中加入例如以下内容:
- <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
- tempDir="/tmp/war-temp/"
- deployDir="/tmp/war-deploy/"
- watchDir="/tmp/war-listen/"
- watchEnabled="false"/>
在Tomcat的官方文档(Tomcat 6)中,对于<Deployer />节点的部署位置是错误的,通过观察Tomcat启动日志。确认该节点应当不属于<Host />节点中!
注意:Tomcat 6与Tomcat5在上述节点中使用的类包(包中名称由cluster变化为ha)有所不同。且结构有所调整。
先别急着重新启动,我们须要改动应用中的web.xml文件。将<distributable />节点部署到<web-app />节点中,开启分布式服务:
注意:假设没有设置该节点,SessionID将不能保持同步。不同的服务器将各自建立独立的SessionID!
监控Tomcat日志:
- tail -f /var/lib/tomcat6/logs/catalina.out
然后重新启动Tomcat1:
- sudo /etc/init.d/tomcat6 restart
观察日志:
注意两处红框:
第一处。Cluster启动,并绑定192.168.49.132:4000上。进行TCP通讯。并等待其它成员(Member)。
第二处,在管理器中注冊/zlex,绑定JvmRouteBinderValve。
至此。说明集群设置已经生效,但不能说明集群配置成功!
接着我们启动Tomcat2,观察其日志:
Cluster启动。并绑定192.168.49.128:4000上并发现成员[b]192.168.49.132![/b]
再看Tomcat1的日志:
Tomcat1发现其成员Tomcat2!这说明TCP通讯已建立。Tomcat成员能够进行Session同步!
同一时候,Tomcat成员直接会每隔一个时间段相互侦測/验证其它成员是否正常:
如今,開始訪问http://localhost/zlex。并不断刷新当前页面:
除了两处标识主机来源的tomcatX不同外。session是全然一致的!
提交一次改动。并不断刷新当前页:
假设细致观察,当前SessionID在不断交替变化,这说明负载均衡在起作用!
我们再来看看2个Tomcat后台日志都做了什么!
Tomcat1:
Tomcat2:
两仅仅猫都打印了同样的内容(a=1)不同的细节在于,sessionID带有服务器标识!
假设我们强行关闭Tomcat2:
首先,Tomcat1会非常快侦測到Tomcat2离线,因为这是TCP通讯。成员之间非常easy检測到其它成员是否离线!Tomcat1后台日志例如以下:
其次。Apache会侦測到Tomcat2发生异常。将其余请求转交给其它节点。即交由Tomcat1处理!
继续刷新http://localhost/zlex当前页面,耐心等待几秒。你会发现,即便再次刷新页面。sessionID仍旧绑定在标识tomcat1服务器上。
然后,我们恢复Tomcat2服务。Tomcat1会立即侦測到Tomcat2已经恢复正常:
最后。我们再次刷新当前页。Apache已经将请求分发给Tomcat2了,从后台日志能够看到session信息会非常快被同步了。
假设带有tomcatX标识的sessionID有非常多不便之处,能够关闭粘性会话。简单的讲。就是取消Tomcat中[b]server.xml中<Engine/ >节点的jvmRoute属性。[/b]然后。重新启动tomcat、apache!
页面提交一个b=3!
左边为Tomcat1,右边为Tomcat2。SessionID一致!
除了上述几种方案外,还有Terracotta模式。
一种第三方集群组件,2009年收购了缓存组件EhCache,能够结合Tomcat、JBoss等多种服务器,提供多种负载均衡、集群等功能实现,且当负载均衡节点超过8个时。仍然能够保持集群吞吐量的线性增长。
Eclipse插件地址:
http://download.terracotta.org/eclipse/update
下载地址:
http://www.terracotta.org/dl/oss-download-catalog
至此,Apache + Tomcat成功完毕,征服Apache系列暂告一段落。
作为开博以来的第100帖,算是非常成功了!
以上是关于Apache + Tomcat 负载均衡 session复制的主要内容,如果未能解决你的问题,请参考以下文章