在第一部分中,我简述了具有可升级和高可靠性的大型J2EE系统在设计时需要考虑的各种因素。

讨论Tomcat对集群、负载均衡、容错和 session 复制等能力的支持。

在这个部分,我们将看到完整一个集群的架构和部署集群过程的安装和配置细节(通过运行多个Tomcat服务器实例)。

+ 集群的设置

下面列出的是这个 Tomcat 集群例子要实现的目标:

* 可升级能力

* 容错

* 动态配置,易于管理

* 自动发现新成员

* 失败重启和负载均衡,session数据内存复制

* 可插拔/配置的负载均衡策略

* 当一个成员加入或离开时,能通知组成员

* 通过多播的方式,无掉包的信息传输

* 集群对 web 应用和服务器来说都是无缝的。

在这个集群环境中,安装有四个 Tomcat 服务器实例。一个作负载均衡服务器,三个作集群。

集群以垂直缩放的方法设置(多个 Tomcat 服务器实例运行在一台机器上)。

下面是集群的主要组成部分的设置:

* 负载均衡 : 一个Tomcat实例,分发交易到集群的个节点上。代号TC-LB

* 集群 : 集群包含3Tomcat服务器实例,代号分别是 TC01, TC02 TC03

* session 持久化 : 选择内存复制的方式。当session对象改变时,session数据将被复制到所有3个集群成员。

* 失败重启 : Tomcat安装时自带的负载均衡器应用不能处理失败重启。

我写了一个工具类 ServerUtil ,在转发请求给服务器之前检查服务器状态。

有种两种方法检查集群节点的状态。在第一种方法中,使用 McastService 来检测是否有一个指定的服务器实例运行。

而第二种方法则通过以Web页的URL为参数创建一个URL对象,验证集群节点的有效性。

要使用这个类,需要确保 catalina-cluster.jar(位于 %TOMCAT_HOME%/server/ 库目录)

commons-logging-api.jar(位于%TOMCAT_HOME%/bin 目录) 文件在 classpath 中指定。

下面集群的主要组件的架构图。

architecturediagram.gif

1Tomcat集群架构图

+ 安装和配置 Tomcat实例

1,本例中设置 Tomcat 集群环境所用到的硬件和软件

Processor HP Pavilion Pentium III with 800 MHz

Memory 512 MB RAM

Hard Disk 40 GB

Operating System Windows 2000 server with Service Pack 4

JDK Version 1.4.0_02 (Note: JDK version 1.4 or a later version is required to enable Tomcat Clustering)

Tomcat Version 5.0.19

Tools Used Ant 1.6.1, Log4J, JMeter, JBuilder

+ 集群框架的主要元素

++ Java

* BaseLoadBalancingRule

抽象类,封装通用的规则逻辑。在这个例子中的自定义负载均衡规则就是扩展自这个基类。

* RandomRedirectRule

使用“随机”的规则,定义重定向web请求到一个有效的服务器上的逻辑。使用当前系统时间作为种子,生成一个随机的号码。

* RoundRobinRule

这个类定义一个负载均衡的逻辑,基于“轮循”规则。当一个请求进入,它将其重定向到集群成员列表中的下一个成员。

使用一个静态变量来跟踪下一个有效的集群成员,每处理一个请求,就将这个值加1

* ServerUtil

一个工具类,用来检测指定的集群节点是否有效。

这个类用 McastService org.apache.catalina.cluster.mcast 包)来检测某集群成员是否离开了这个组。

下面的类图表示这些Java类之间的关系。

classdiagram.gif

2 集群应用类图

++ 配置文件

* server.xml

用于对 Tomcat 服务器实例进行集群配置。这个版本的Tomcat安装后,server.xml文件中包含被注释掉的集群配置细节。

* web.xml

在这个文件中可指明该web应用的session数据需要被复制。

* rules.xml

这个文件用来定义的负载均衡规则。

++ 脚本

* test.jsp

一个简单的测试 JSP 脚本,用于检查服务器的状态。显示运行的Tomcat实例的名字和系统时间。

* testLB.jsp

在本应用中,这个是起始页面。它使用 HTML 重定向将web请求转发到负载均衡过滤器上。

* sessiondata.jsp

这个脚本用来验证当一个集群节点挂起时,session数据并没有丢失。显示session的内容,使用 HTML 字段操作 HTTP session 对象。

* build.xml

Ant build 脚本,让启动和停止Tomcat实例的任务实现自动化(由Ant 1.6.1 用来执行这个脚本)。一旦某个Tomcat实例启动成功,你可以通过指定IP地址和端口号,调用test.jsp来验证该Tomcat实例是否在运行。这个JSP页将显示当前系统时间和Tomcat实例的名称。你需要改变 build.properties 文件中的 home 目录的指定,在你自己的环境中运行这个脚本。

build 脚本中用于启动或停止 Tomcat 实例的几个 targets:

* 调用 target “start.tomcat5x” 启动一个特定的 Tomcat 实例(例如: tomcat50)

* 调用 stop.tomcat5x 停止一个特定的Tomcat实例

* 调用 stop.alltomcats 中止所有运行的 Tomcat 实例

+ 范例代码

本例子的代码 tomcatclustering.zip。安装完 Tomcat 服务器实例后(4),解压这个zip文件中的文件到tomcat目录。

例子代码使用RoundRobinRule作为负载均衡规则。如果您想使用随机的重定向规则,修改rules.xml文件(tomcat50/webapps/balancer/WEB-INF/conf目录中)

注释掉 关于 RoundRobinRule 的元素,取消关于 RandomRedirectRule 元素的注释。 同样,如果您想用两个实例,而不是三个,注释掉第三个,并改变maxServerInstances属性的值为2(替换原来的3)。

注意:缺省情况下,tomcat安装后会包含好几个其他的应用,我删除了所有其他的web应用(jsp-examples,等等),仅仅保留 balancer 和 本例的web应用。

HTTP 请求流程

本例集群环境中的 web请求流程如下:

1. 运行起始页面(http://localhost:8080/balancer/testLB.jsp)

2. JSP将请求重定向到负载均衡过滤器(URL:http://localhost:8080/balancer/LoadBalancer)

3. 负载均衡器(TC-LB)拦截web请求,并根据配置文件中指定的负载均衡规则重定向到下一个有效的集群成员(TC01, TC02 或者 TC03)

4. 被选中的集群成员的sessiondata.jsp (位于 “clusterapp” web应用)被调用;

5. 如果 session 被修改, ClusterAppSessionListener session 监听器方法将被调用,用于记录 session 修改事件;

6. sessiondata.jsp web浏览器上显示session的详细内容(例如session id,最后访问事件,等等)

7. 随机停止一个或两个集群节点(调用 Ant 脚本的 “stop.tomcat5x” target );

8. 重复上面7个步骤,查看是否对某个有效的集群成员的请求失败。同时,检查session信息是否在集群成员内部进行无数据丢失的拷贝。

sequencediagram.gif

3 表示一个web请求的流程

集群应用的序列图

+ 集群的配置

在这个集群中,运行一个“clusterapp”web应用。为优化session复制,所有的实例拥有一样的目录结构和内容。

由于Tomcat服务器实例使用IP多播来传输session,我们必须确定集群机器上的IP多播功能是可用的。为验证,你可以运行《如果编写多播服务和客户程序。Tomcat:The Definitive Guide》这本书中的例子Java程序MulticastNode,或者,参考http://java.sun.com/docs/books/tutorial/networking/datagrams/broadcasting.html

当一个集群节点启动,集群中的其他成员将在服务器控制台上显示一条记录信息,说明一个成员已经被添加到集群中。类似的,当一个集群节点下线,其他的节点将在控制台上显示一个集群成员离开的记录。

clustercommunication.gif

4 当集群中添加或者删除一个成员时所产生的记录信息

按照下面的步骤可打开Tomcat服务器的集群和session复制功能:

1.所有的session属性必须实现java.io.Serailizable接口

2.取消对server.xml文件中Cluster元素的注释。userDirtyFlagreplicationMode两个属性用于优化频率和session复制机制。

3.取消对server.xmlValue元素的注释。ReplicationValue用于拦截HTTP请求并在集群成员内复制session数据。Value元素有一个“filter”的属性,可以用来过滤不会对session进行修改的请求(HTML页面和图像文件)

4.由于全部Tomcat实例都是运行在同一台机器上,每个Tomcat实例的tcpListenPort属性需要设置成唯一。名字格式为 mcastXXX(mcastAddr, mcastPort, mcastFrequency, mcastDropTime) 的属性都是用于集群关系的多播ping,而名字格式为tcpXXX(tcpThreadCount, tcpListenAddress, tcpListenPort tcpSelectorTimeout) 是用于 session 复制(下面的集群配置参数表显示Tomcat服务器实例的不同配置)

5.web.xml meta文件(位于clusterapp\WEB-INF目录)应该拥有元素。为一个指定的web应用复制session状态,distributable元素必须被定义。这表示如果你有不止一个web应用需要session复制,那么你需要增加distributable到所有web应用的web.xml文件中。《Tomcat:The Definitive Guide》这本书的“Tomcat集群”这章对这个问题有很好的解释。

2 集群的配置参数

配置参数 实例 1 实例 2 实例 3 实例 4
Instance Type 负载均衡器 集群节点1 集群节点2 集群节点3
Code name TC-LB TC01 TC02 TC03
Home Directory c:/web/tomcat50 c:/web/tomcat51 c:/web/tomcat52 c:/web/tomcat53
Server Port 8005 9005 10005 11005
Connector 8080 9080 10080 11080
Coyote/JK2 AJP Connector 8009 9009 10009 11009
Cluster mcastAddr 228.0.0.4 228.0.0.4 228.0.0.4 228.0.0.4
Cluster mcastPort 45564 45564 45564 45564
tcpListenAddress 127.0.0.1 127.0.0.1 127.0.0.1 127.0.0.1
Cluster tcpListenPort 4000 4001 4002 4003

注意:由于所有的集群成员都是运行在同一台机器上,他们使用同一个IP地址(127.0.0.1)

如果你没有使用Ant脚本启动和停止Tomcat实例,不要在你的机器上设置CATALINA_HOME环境变量。如果这个变量被设置,所有的实例都尝试使用同一个目录(CATALINA_HOME 变量指定的)来启动Tomcat实例。结果只有第一个实例能成功启动,其他的实例会崩溃,出现邦定异常信息,通知端口已经被使用:“java.net.BindException: Address already in use: JVM_Bind:8080”

+ 负载均衡的设置

我写了两个简单,自定义的负载均衡规则(RoundRobinRule RandomRedirect),用于重定向进入的web请求。这些规则都是基于负载均衡算法(例如轮循和随机重定向)。你可以编写基于其他因素(如加权和最后访问时间等)类似的自定义负载均衡规则。Tomcat 负载均衡器提供一个样例(基于参数的负载均衡规则),它根据HTTP请求的参数决定重定向web请求到不同的URL上。

保持 server.xml (TC-LB实例) 中关于集群和value元素的注释状态,因为该实例并非集群成员。

+ 测试的设置

++ session持久化测试

session持久化测试中, 主要目标是在一个web请求过程中验证当一个集群成员崩溃后,session数据并没有丢失。JSP sessiondata.jsp 用来显示session内容。这个脚本同时提供HTML text字段,用于添加/修改/删除 session属性。在添加属性给HTTP session后,我随机的停止集群节点,并检测有效的集群成员上的session

++ 负载测试

负载测试的目的是研究自定义的负载均衡算法,当一个或多个节点停止服务的情况下,web请求如何被有效的分发到指定的集群节点。JMeter负载测试工具就是用来模拟多并发web用户的情况。

测试负载均衡的步骤如下:

1. 启动负载均衡器和集群实例。

2. 运行起始JSP 脚本(testLB.jsp)。

3. 通过手动停止一个或者多个容器来模拟服务器崩溃。

4. 检查负载分发模式。

5. 重复100次步骤14

所有的记录信息被重定向到一个文本文件,叫tomcat_cluster.log(位于 tomcat50/webapps/balancer目录)。在序列图中(图2)的所有web对象的响应时间是使用Log4J信息记录。表3是耗时(毫秒)表。

下表表示负载测试的耗时(使用RoundRobinRule算法)和负载分发百分比(使用RandomRedirectRule算法)。

Table 3. 负载测试的耗时

# Scenario testLB.jsp
(ms)
RoundRobinRule
(ms)
sessiondata.jsp
(ms)
Total
(ms)
1 三个服务器都在运行 54 76 12 142
2 两个服务器实例在运行(TC02 was stopped) 55 531 14 600
3 一个服务器在运行
(TC01 and TC02 were stopped)
56 1900 11 1967

注意:所有的耗时是100个并发用户的平均值。

4. 当使用随机负载均衡规则是的负载分发。

# Scenario TC01 (%) TC02 (%) TC03 (%)
1 所有服务实例在运行 30 46 24
2 两个服务实例在运行 (TC02 was stopped) 56 0 44

注意:负载分发的百分比也是基于100个并发用户的负载。

+ 总结

session持久化测试中,增加session属性后,其中的一个集群节点挂起,通过验证,证实在服务器停机时间,session属性并没有丢失。session属性的具体内容记录在文本文件中。

在负载测试中,当一个或者两个服务器实例停止,仅有一个 Tomcat 实例运行,回应的时间比起所有三个实例都有效时长。当原先停止的实例重新启动,负载均衡器自动重新发现这些服务器有效,将接下来的请求重定到这些服务器实例上,马上能提高回应的时间。

这里用来发现集群成员是否有效的机制(ServerUtil)并非是最快的方法。

这个集群设置的一个缺陷是它仅仅提供一个负载均衡器。当用作负载均衡器的 Tomcat 实例挂起时会发生什么事情呢?就没有途径转发请求到集群,这个结果叫做单点失败(SPoF).其中一个解决方法就是有另外一个Tomcat实例运行着,作为一个备用负载均衡器。如果主负载均衡器崩溃时,备用均衡器将接替它的工作。典型的 高可靠性集群(HA) 包含两个负载均衡器防止 SPoF 的情况发生。

在上面的例子中,所有Tomcat实例(包括负载均衡器)都是配置在同一台机器上运行。更好的设置就是在一台独立的机器上运行负载均衡器。同样,限制每台机器拥有两个集群节点,充分利用水平缩放的方法来保证集群的效率。

J2EE Web 应用服务器来讲,HTTP session 复制是一种昂贵的操作。J2EE集群环境下,session管理的实现应该在项目的分析和设计阶段中就需要考虑。编码时必须想着集群环境。如果没有在设计阶段就考虑集群的实现,为了让应用能在集群环境下工作,代码可能需要全部重写。这会造成非常大的影响。

如果web应用支持各种对象缓存机制,那么在应用开发的初始阶段,集群环境中的缓存对象就应该被考虑。这是非常重要的,因为对于提供精确和即时的事务数据给Web用户,在所有集群的节点中保持缓存数据的同步是非常危险的。

一旦J2EE集群成功设置和运行,它的管理和维护将变得非常重要。保持集群的运行和将应用的变化推到所有的集群节点上。需要有一个方法提供这些服务,实现一个监视器服务,周期性的检测服务器的有效性,如果集群中有节点无效,它将会发出通知。这个服务有规律间隔的检测失效的节点,并从活动集群节点列表中删除失效的节点。它应该拥有一种能力,当改变和更新出现时,它能同步和更新集群中所有的服务器。由于对web应用的所有的请求必须通过负载均衡系统,这个系统能检测到活动session的数量,活动session的数量,回应次数,高峰负载的次数,高峰其间活动session的数量,低谷其间活动负载的数量,等等。这些审计信息可以为提高性能,优化整个系统作为参考。

在这里,可以通过手动调整配置文件(server.xml rules.xml)满足设置集群和负载均衡器的所有配置需求。如果Jakarta项目组提供基于Web的集群管理工具,那我们就可以通过使用管理工具修改配置来管理集群和负载均衡。

+ 资源