英文原版 ,中文版由 Tony Tang 翻译整理
在第一部分中,我简述了具有可升级和高可靠性的大型 J2EE 系统在设计时需要考虑的各种因素。
讨论 Tomcat 对集群、负载均衡、容错和 session 复制等能力的支持。
在这个部分,我们将看到完整一个集群的架构和部署集群过程的安装和配置细节(通过运行多个 Tomcat 服务器实例)。
+ 集群的设置
下面列出的是这个 Tomcat 集群例子要实现的目标:
* 可升级能力
* 容错
* 动态配置,易于管理
* 自动发现新成员
* 失败重启和负载均衡, session 数据内存复制
* 可插拔 / 配置的负载均衡策略
* 当一个成员加入或离开时,能通知组成员
* 通过多播的方式,无掉包的信息传输
*
集群对
web
在这个集群环境中,安装有四个 Tomcat 服务器实例。一个作负载均衡服务器,三个作集群。
集群以垂直缩放的方法设置(多个 Tomcat 服务器实例运行在一台机器上)。
下面是集群的主要组成部分的设置:
* 负载均衡 : 一个 Tomcat 实例,分发交易到集群的个节点上。代号 TC-LB 。
* 集群 : 集群包含 3 个 Tomcat 服务器实例,代号分别是 TC01, TC02 和 TC03 。
* session
持久化
:
* 失败重启 : Tomcat 安装时自带的负载均衡器应用不能处理失败重启。
我写了一个工具类 ServerUtil ,在转发请求给服务器之前检查服务器状态。
有种两种方法检查集群节点的状态。在第一种方法中,使用 McastService 来检测是否有一个指定的服务器实例运行。
而第二种方法则通过以 Web 页的 URL 为参数创建一个 URL 对象,验证集群节点的有效性。
要使用这个类,需要确保 catalina-cluster.jar( 位于 %TOMCAT_HOME%/server/ 库目录 )
和 commons-logging-api.jar( 位于 %TOMCAT_HOME%/bin 目录 ) 文件在 classpath 中指定。
下面集群的主要组件的架构图。
图 1 , Tomcat 集群架构图
+ 安装和配置 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 类之间的关系。
图 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 信息是否在集群成员内部进行无数据丢失的拷贝。
图 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 。
当一个集群节点启动,集群中的其他成员将在服务器控制台上显示一条记录信息,说明一个成员已经被添加到集群中。类似的,当一个集群节点下线,其他的节点将在控制台上显示一个集群成员离开的记录。
图 4 当集群中添加或者删除一个成员时所产生的记录信息
按照下面的步骤可打开 Tomcat 服务器的集群和 session 复制功能:
1. 所有的 session 属性必须实现 java.io.Serailizable 接口
2. 取消对 server.xml 文件中 Cluster 元素的注释。 userDirtyFlag 和 replicationMode 两个属性用于优化频率和 session 复制机制。
3. 取消对 server.xml 中 Value 元素的注释。 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 次步骤 1 至 4 。
所有的记录信息被重定向到一个文本文件,叫 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 的集群管理工具,那我们就可以通过使用管理工具修改配置来管理集群和负载均衡。
+ 资源
* Tomcat: The Definitive Guide by Jason Brittain and Ian F. Darwin
* Java Performance Tuning, 2nd Edition by Jack Shiraji
* Creating Highly Available and Scalable Applications Using J2EE, The Middleware Company, EJB Essentials Training Class Material