`

Tomcat集群(使用了nginx负载)

阅读更多
前一篇文章介绍了使用nginx进行负载均衡的方式,本篇开始搭建Tomcat的负载,前端采用nginx进行负载(参考上一篇)

    对于WEB应用集群的技术实现而言,最大的难点就是如何能在集群中的多个节点之间保持数据的一致性,会话(Session)信息是这些数据中最重要的一块。要实现这一点,大体上有两种方式,一种是把所有Session数据放到一台服务器上或者数据库中,集群中的所有节点通过访问这台Session服务器来获取数据;另一种就是在集群中的所有节点间进行Session数据的同步拷贝,任何一个节点均保存了所有的Session数据。两种方式都各有优点,第一种方式简单、易于实现,但是存在着Session服务器发生故障会导致全系统不能正常工作的风险;第二种方式可靠性更高,任一节点的故障不会对整个系统对客户访问的响应产生影响,但是技术实现上更复杂一些。常见的平台或中间件如microsoftasp.net和IBM WAS都会提供对两种共享方式的支持,tomcat也是这样,但是一般采用第二种方式。

搭建Tomcat集群
1.首先对Tomcat进行配置
配置tomcat的server.xml。修改 <Engine name="Catalina" defaultHost="localhost">添加jvmRoute标示: <Engine name="Catalina" defaultHost="localhost" jvmRoute="Tomcat1">
在Engine节点下,添加集群配置(各配置项的意义参考tip2):
<Engine name="Catalina" defaultHost="localhost" jvmRoute="Tomcat1">
  <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"     <!--组播地址,参看tip1-->
                        port="45564"     
                        frequency="500"     
                        dropTime="3000"/>
        <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"     
                      address="auto"   #默认为auto,改为自己的IP   
                      port="14000"    #同一台服务器上的tomcat必须修改为不同的端口,tomcat1修改为4001,tomcat2修改为4002。注意修改配置时,要把#后面的内容删掉,要不然会启动报错
                          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>
...


2.启动两个Tomcat,会看到类似下面的信息
信息: Replication member added:org.apache.catalina.tribes.membership.MemberImpl[
tcp://{192, 168, 1, 101}:14000,{192, 168, 1, 101},14000, alive=1047, securePort=
-1, UDP Port=-1, id={87 -12 -72 -127 82 63 78 112 -121 -75 125 -56 32 63 26 48 }
, payload={}, command={}, domain={}, ]
启动时看到的大致如下:


关闭其中一个时,另一个的信息如下:



3.新建测试项目
有两点注意事项:1.如果有自定义session对象的话,要继承自serializable接口;2.在web.xml中添加<distributable />标签。测试项目比较简单,只包含一个jsp页面,懒得写的童鞋可以再附件中下载测试项目(ClusterValidate.rar)。
test.jsp代码:
<%@ page contentType="text/html; charset=GBK"%>
<%@ page import="java.util.*"%>
<html>
<head>
<title>Cluster App Test</title>
</head>
<body>
	Server Info:
	<%
	out.println(request.getLocalAddr() + " : " + request.getLocalPort()
			+ "<br>");
%>
	<%
		out.println("<br> ID " + session.getId() + "<br>");
		// 如果有新的 Session 属性设置
		String dataName = request.getParameter("dataName");
		if (dataName != null && dataName.length() > 0) {
			String dataValue = request.getParameter("dataValue");
			session.setAttribute(dataName, dataValue);
		}
		out.println("<b>Session 列表</b><br>");
		System.out.println("============================");
		Enumeration<String> e = session.getAttributeNames();
		while (e.hasMoreElements()) {
			String name = e.nextElement();
			String value = session.getAttribute(name).toString();
			out.println(name + " = " + value + "<br>");
			System.out.println(name + " = " + value);
		}
	%>

	<form action="test.jsp" method="POST">
		名称:<input type=text size=20 name="dataName"> <br> 
		值:<input type=text size=20 name="dataValue"> <br> 
		<input type=submit>
	</form>
</body>
</html>



4.测试
上一篇文章中配置好的nginx,端口是8080,映射到18080和28080端口。
访问地址:
http://localhost:8080/ClusterValidate/test.jsp,多提交几次,可以看到请求到不同的tomcat了,但是sessionid是一致的。

-------------------------------------------------------------
over
-------------------------------------------------------------

Tips:
Tip1 组播地址:
IP组播地址,或称为主机组地址,由D类IP地址标记。D类IP地址的最高四位为“1110”,起范围从224.0.0.0到239.255.255.255。如前所述,部分D类地址被保留,用作永久组的地址,这段地址从224.0.0.0-224.0.0.255。比较重要的地址有:

     224.0.0.1 - 网段中所有支持组播的主机
     224.0.0.2 - 网段中所有支持组播的路由器
     224.0.0.4 - 网段中所有的DVMRP路由器
     224.0.0.5 - 所有的OSPF路由器
     224.0.0.6 - 所有的OSPF指派路由器
     224.0.0.9 - 所有RIPv2路由器
     224.0.0.13 -所有PIM路由器

    临时主机组的组播地址由网络管理员选择,他需要保证这个地址在一定的范围内没有其他的主机组在使用这个组播地址。

     第2层的组播地址(组播MAC地址)可以从IP组播地址中衍生。计算方法是把IP地址的最后23位拷贝到MAC地址的最后23位,然后把这23位前面的那一位置为0。MAC地址的前24位必须为0x01-00-5E。例如:组播IP地址224.0.1.128,16进制表示为0xE0-00-01-10,最低的23位为0x00-01-10,计算得出的MAC地址为:0x01-00-5E-00-01-10。

Tip2 Tomcat Cluster各配置项的意义
    tomcat集群各节点通过建立tcp链接来完成Session的拷贝,拷贝有同步和异步两种模式。在同步模式下,对客户端的响应必须在Session拷贝到其他节点完成后进行;异步模式无需等待Session拷贝完成就可响应。异步模式更高效,但是同步模式可靠性更高。同步异步模式由channelSendOptions参数控制,默认值是8,为异步模式,4是同步模式。在异步模式下,可以通过加上拷贝确认(Acknowledge)来提高可靠性,此时channelSendOptions设为10。
    Manager用来在节点间拷贝Session,默认使用DeltaManager,DeltaManager采用的一种all-to-all的工作方式,即集群中的节点会把Session数据向所有其他节点拷贝,而不管其他节点是否部署了当前应用。当集群中的节点数量很多并且部署着不同应用时,可以使用BackupManager,BackManager仅向部署了当前应用的节点拷贝Session。但是到目前为止BackupManager并未经过大规模测试,可靠性不及DeltaManager。
    Channel负责对tomcat集群的IO层进行配置。Membership用于发现集群中的其他节点,这里的address用的是组播地址(Multicastaddress),使用同一个组播地址和端口的多个节点同属一个子集群,因此通过自定义组播地址和端口就可将一个大的tomcat集群分成多个子集群。Receiver用于各个节点接收其他节点发送的数据,在默认配置下tomcat会从4000-4100间依次选取一个可用的端口进行接收,自定义配置时,如果多个tomcat节点在一台物理服务器上注意要使用不同的端口。Sender用于向其他节点发送数据,具体实现通过Transport配置,PooledParallelSender是从tcp连接池中获取连接,可以实现并行发送,即集群中的多个节点可以同时向其他所有节点发送数据而互不影响。Interceptor有点类似下面将要解释的Valve,起到一个阀门的作用,在数据到达目的节点前进行检测或其他操作,如TcpFailureDetector用于检测在数据的传输过程中是否发生了tcp错误。关于Channel的编程模型,请参见http://tomcat.apache.org/tomcat-6.0-doc/api/org/apache/catalina/tribes/Channel.html。
    Valve用于在节点向客户端响应前进行检测或进行某些操作,ReplicationValve就是用于用于检测当前的响应是否涉及Session数据的更新,如果是则启动Session拷贝操作,filter用于过滤请求,如客户端对图片,css,js的请求就不会涉及Session,因此不需检测,默认状态下不进行过滤,监测所有的响应。JvmRouteBinderValve会在前端的Apachemod_jk发生错误时保证同一客户端的请求发送到集群的同一个节点,tomcat官方文档并未解释如何实现这一点,而且笔者认为这一设置似乎并无多大实用性。
    Deployer用于集群的farm功能,监控应用中文件的更新,以保证集群中所有节点应用的一致性,如某个用户上传文件到集群中某个节点的应用程序目录下,Deployer会监测到这一操作并把这一文件拷贝到集群中其他节点相同应用的对应目录下以保持所有应用的一致。这是一个相当强大的功能,不过很遗憾,tomcat集群目前并不能做到这一点,开发人员正在努力实现它,这里的配置只是预留了一个接口。
   Listener用于跟踪集群中节点发出和收到的数据,也有点类似Valve的功能。

Tip3 tomcat集群工作机制
下面通过假想的一组场景来描述tomcat集群如何工作,集群采用默认配置,由t1和t2两个tomcat例程组成,场景按照时间顺序排列。
1. t1启动
     t1按照标准的tomcat启动,当Host对象被创建时,一个Cluster对象(默认配置下是SimpleTcpCluster)也同时被关联到这个Host对象。当某个应用在web.xml中设置了distributable时,Tomcat将为此应用的上下文环境创建一个DeltaManager。SimpleTcpCluster启动membership服务和Replication服务(用于建立tcp连接)。

2. t2启动(待t1启动完成后)
     首先t2会执行和t1一样的操作,然后SimpleTcpCluster会建立一个由t1和t2组成的membership。接着t2向集群中已启动的服务器即t1请求Session数据,如果t1没有响应t2的拷贝请求,t2会在60秒后time out。在Session数据拷贝完成之前t2不会接收客户端的http或mod_jk/ajp请求。

3. t1接收http请求,创建Session s1
     t1正常响应客户请求,但是在t1把结果发送回客户端时,ReplicationValve会拦截当前请求(如果filter中配置了不需拦截的请求类型,这一步就不会进行,默认配置下拦截所有请求),如果发现当前请求更新了Session,调用Replication服务建立tcp连接把Session拷贝到membership列表中的其他节点即t2,返回结果给客户端(注意,如果采用同步拷贝,必须等拷贝完成后才会返回结果,异步拷贝在数据发送到tcp连接就返回结果,不等待拷贝完成)。在拷贝时,所有保存在当前Session中的可序列化的对象都会被拷贝,而不仅仅是发生更新的部分。

4. t1崩溃
     当t1崩溃时,t2会被告知t1已从集群中退出,然后t2就会把t1从自己的membership列表中删除,发生在t2的Session更新不再往t1拷贝,同时负载均衡器会把后续的http请求全部转发给t2。在此过程中所有的Session数据不会丢失。

5. t2接收s1的请求
     t2正常响应s1的请求,因为t2保存着s1的所有数据。

6. t1重新启动
     按步骤1、2一样的操作启动,加入集群,从t2拷贝所有Session数据,拷贝完成后开放自己的http和mod_jk/ajp端口接收请求。

7. t1接收请求,s1失效
     t1继续接收来自s1的请求,把s1设置为过期。这里的过期并非因为s1处于非活动状态超过设置的时间,而是执行类似注销的操作而引起的Session失效。这时t1并非发送s1的所有数据而是一个类似s1 expired的消息,t2收到消息后也会把s1设为过期。

8. t2接收请求,创建Session s2
     和步骤3一样。

9. t1 s2过期
     对于因超时引起的Session失效t1无需通知t2,因为t2同样知道s2已经超时。因此对于tomcat集群有一点非常重要,所有节点的操作系统时间必须一致!不然会出现某个节点Session已过期而在另一节点此Session仍处于活动状态的现象。


参考资料:
http://xmong.iteye.com/blog/1288740
http://hi.baidu.com/dc_life/item/e2e03c3438ded6c82e8ec249
http://zyycaesar.iteye.com/blog/296501
http://www.iteye.com/topic/1017961
  • 大小: 260.8 KB
  • 大小: 45.9 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics