在第一部分中,我簡述了具有可升級和高可靠性的大型J2EE系統在設計時需要考慮的各種因素。
討論Tomcat對集群、負載均衡、容錯和 session 復制等能力的支持。
在這個部分,我們將看到完整一個集群的架構和部署集群過程的安裝和配置細節(通過運行多個Tomcat服務器實例)。
+ 集群的設置
下面列出的是這個 Tomcat 集群例子要實現的目標:
* 可升級能力
* 容錯
* 動態配置,易于管理
* 自動發現新成員
* 失敗重啟和負載均衡,session數據內存復制
* 可插拔/配置的負載均衡策略
* 當一個成員加入或離開時,能通知組成員
* 通過多播的方式,無掉包的信息傳輸
* 集群對 web 應用和服務器來說都是無縫的。
在這個集群環境中,安裝有四個 Tomcat 服務器實例。一個作負載均衡服務器,三個作集群。
集群以垂直縮放的方法設置(多個 Tomcat 服務器實例運行在一臺機器上)。
下面是集群的主要組成部分的設置:
* 負載均衡 : 一個Tomcat實例,分發交易到集群的個節點上。代號TC-LB。
* 集群 : 集群包含3個Tomcat服務器實例,代號分別是 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 中指定。
下面集群的主要組件的架構圖。

圖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的集群管理工具,那我們就可以通過使用管理工具修改配置來管理集群和負載均衡。
+ 資源