實戰Apache+Tomcat集群和負載均衡
目錄
1. 什么是J2EE集群... 3
1.1. 序言... 3
1.2. 基本術語... 3
伸縮性(Scalability):... 4
高可用性(High availability):... 4
負載均衡(Load balancing):... 4
容錯(Fault tolerance):... 5
失效轉移(Failover):... 5
等冪方法(Idempotent methods):... 5
1.3. 什么是J2EE集群... 5
2. J2EE集群給我們帶來了什么... 9
3. 實戰准備... 9
4. 首戰失敗... 9
5. 詳細配置... 9
6. 負載均衡... 9
7. 失敗轉移... 9
1. 什么是J2EE集群
1.1. 序言
越來越多的關鍵應用運行在J2EE(Java 2, Enterprise Edition)中,這些諸如銀行系統和賬單處理系統需要高的可用性(High Availability, HA),同時像Google和Yahoo這種大系統需要大的伸縮性。高可用性和伸縮性在今天高速增長的互連接的世界的重要性已經證實了。eBay於1999年6月停機22小時的事故,中斷了約230萬的拍賣,使eBay的股票下降了9.2個百分點。
J2EE集群是用來提供高可用性和伸縮性服務,同時支持容錯處理的一種流行的技術。但是,由於J2EE規范缺乏對集群的支持,J2EE供應商實現集群的方法也各異。這給J2EE架構師和開發人員帶來了很多困難。以下是幾個常見的問題:
l 為什么帶集群功能的商業J2EE服務器產品如此昂貴?(10倍於不帶集群功能的產品)
l 為什么基於單服務器環境構建的應用不能在集群中運行?
l 為什么應用在集群環境中運行得很慢,但在非集群環境中卻快得多?
l 為什么集群的應用移植到其他服務器中失敗?
l 理解這些限制和要素的最佳方法是學習他們的實現方式。
1.2. 基本術語
在我們討論不同的集群實現之前,先談談幾個概念。這有助於理解不同的J2EE集群產品不同的設計結果和概念:
1.2.1. 伸縮性(Scalability):
在一些大的系統中,預測最終用戶的數量和行為是非常困難的,伸縮性是指系統適應不斷增長的用戶數的能力。提高這種並發會話能力的一種最直觀的方式就增加資源(CPU,內存,硬盤等),集群是解決這個問題的另一種方式,它允許一組服務器組在一起,像單個服務器一樣分擔處理一個繁重的任務。
1.2.2. 高可用性(High availability):
單一服務器的解決方案並不是一個健壯方式,因為容易出現單點失效。像銀行、賬單處理這樣一些關鍵的應用程序是不能容忍哪怕是幾分鍾的死機。它們需要這樣一些服務在任何時間都可以訪問並在可預期的合理的時間周期內有響應。集群方案通過在集群中增加的冗余的服務器,使得在其中一台服務器失效后仍能提供服務,從而獲得高的可用性。
1.2.3. 負載均衡(Load balancing):
負載均衡是集群的一項關鍵技術,通過把請求分發給不同的服務器,從而獲得高可用性和較好的性能。一個負載均衡器可以是從一個簡單的Servlet或Plug-Ins(例如一個Linux box利用ipchains來實現),到昂貴的內置SSL加速器的硬件。除此之外,負載均衡器還需執行一些其他的重要任務,如“會話膠粘”讓一個用戶會話始終存在一個服務器上,“健康檢查”用於防止將請求分發到已失效的服務器上。有些負載均衡器也會參與我們下面將要談到“失效轉移”過程。
1.2.4. 容錯(Fault tolerance):
高可用性意味着對數據正確性的要求不那么高。在J2EE集群中,當一個服務器實例失效后,服務仍然是有效的,這是因為新的請求將被冗余服務器處理。但是,當一個請求在一個正在失效的服務器中處理時,可能得到不正確的結果。不管有多少個錯誤,容錯的服務應當能確保有嚴格的正確的行為。
1.2.5. 失效轉移(Failover):
失效轉移是集群中用來獲取容錯能力的另一項關鍵的技術。當一個結點失效后,通過選擇集群中的另一個結點,處理將會繼續而不會終止。轉移到另一個結點可以被顯式的編碼,或是通過底層平台自動地透明地路由到另一個服務器。
1.2.6. 等冪方法(Idempotent methods):
等冪方法是指這樣一些方法:重復用相同的參數調用都能得到相同的結果。這些方法不會影響系統狀態,可以重復調用而不用擔心改變系統。例如:getUsername()就是等冪的,而deleteFile就不是。當我們討論HTTP Session失效轉移和EJB失效轉移時,它是一個重要的概念。
1.3. 什么是J2EE集群
一個天真的問題,不是嗎?但我仍要用幾句話和圖來回答它。通常,J2EE集群技術包括"負載均衡"和"失效轉移"。
如圖1所示,負載均衡意味着有許多客戶端向目標對象同時發出請求。負載均衡器在調用者和被調用者之間,分發請求到與原始對象相同的冗余對象中。伸縮性和高可用性就是這樣得到的。
如圖2所示,失效轉移與負載均衡不同。有時客戶端會連續發請求到目標對象,如果請求中間目標對象失效了,失效轉移系統將檢測到這次失敗,並將請求重定向到另一個可用的對象。通過這種方式可以獲得容錯能力。
如果你想知道更多的有關J2EE集群的知識,你就會問到一個基本的問題,“什么對象可以集群?”和“在我的J2EE代碼中哪里會發生負載均衡和失效轉移呢?”。這些都是用來理解J2EE集群的非常好的問題。實際上,並不是所有的對象都能被集群的,並且負載均衡和失效轉移並不是在J2EE代碼所有地方都能發生。看看下面的例子代碼:
在Class A的bussiness()方法中,instance1可以負載均衡嗎?或是當其失效,可以失效轉移到其他B的實例上嗎?我想是不行的!對負載均衡和失效轉移來說,必須要有個攔截器在調用者和被調用者之間分發或重定向請求到不同的對象上。Class A和Class B的實例是運行在一個JVM中緊密耦合的,在方法調用間加入分發邏輯非常困難。
什么類型對象可以被集群?——只有那些可以被部署到分布式拓朴結構中的組件。
在我的J2EE代碼中,什么地方會有負載均衡和失效轉移?——只在你調用分布式組件的方法時。
在如圖4所示的分布式環境中,調用者和被調用者被分離在有明顯邊界的不同的運行容器中,這個邊界可以是JVM,進程和機器。
當目標對象被客戶端調用時,目標對象的功能是在容器中運行的(這就是為什么我們說它是分布式的原因)。客戶端和目標對象通過標准的網絡協議通信。這些特性就為一些機制提供了機會可以介入到方法調用之間實現負載均衡和失效轉移。
如圖4,瀏覽器通過HTTP協議調用JSP對象,JSP運行在WEB服務器中,瀏覽器只需要返回結果而不關心它是怎么運行的。在上述場景中,一些東西就可以在瀏覽器與WEB服務器之間實現負載均衡和失效轉移的功能。在J2EE平台,分布式技術包括:JSP(Servlet),JDBC,EJB,JNDI,JMS,WEB Service等。負載均衡和失效轉移就發生在這些分布式方法被調用時。在后續部分我們將詳細討論這些技術。
2. 實戰
2.1. 軟件環境
2.1.1. Apache
apache 2.0.55 (由http://httpd.apache.org/進入下載)
2.1.2. Tomcat
Tomcat 5.5.25 (由http://tomcat.apache.org/進入下載)
2.1.3. Mod_jk
在頁面 http://tomcat.apache.org/ Download 標題下找到 Tomcat Connectors 鏈接進入( 點擊下載mod_jk-apache-2.0.55.so),看起來像是個Unix/Linux下的動態庫,實際應是個Win32 的 DLL 動態庫,大概是為保持不同平台配置的一致性,才用了這個擴展名。
2.2. 負載均衡
用Apache進行分流,把請求按照權重以及當時負荷分tomcat1,tomcat2...去處理
2.2.1. 安裝apache,tomcat
我把Apache安裝在F:\ZQ\apache2.0.55\Apache2
解壓兩分Tomcat, 分別在 F:\ZQ\apache-tomcat-5.5.1,F:\ZQ\apache-tomcat-5.5.2
2.2.2. 修改Apache配置文件http.conf
在apache安裝目錄下conf目錄中找到http.conf,在文件最后加上下面一句話就可以了
include conf/mod_jk.conf
2.2.3. http.conf 同目錄下新建mod_jk.conf文件,內容如下
#加載mod_jk Module
LoadModule jk_module modules/mod_jk-apache-2.0.55.so
#指定 workers.properties文件路徑
JkWorkersFile conf/workers.properties
#指定那些請求交給tomcat處理,"controller"為在workers.propertise里指定的負載分配控制器
JkMount /*.jsp controller
如果還要指定*.do也進行分流就再加一行
JkMount /*.do controller
如果你想對所有的請求進行分流只需要寫成
JkMount /* controller
2.2.4. 在http.conf同目錄下新建 workers.properties文件,
內容如下(可能要去除 # 不在行首的注釋)
worker.list = controller,tomcat1,tomcat2 #server 列表
#========tomcat1========
worker.tomcat1.port=8009
#ajp13 端口號,在tomcat下server.xml配置,默認8009
worker.tomcat1.host=localhost
#tomcat的主機地址,如不為本機,請填寫ip地址
worker.tomcat1.type=ajp13
worker.tomcat1.lbfactor = 1
#server的加權比重,值越高,分得的請求越多
#========tomcat2========
worker.tomcat2.port=8109
#ajp13 端口號,在tomcat下server.xml配置,默認8009
worker.tomcat2.host=localhost
#tomcat的主機地址,如不為本機,請填寫ip地址
worker.tomcat2.type=ajp13
worker.tomcat2.lbfactor = 2
#server的加權比重,值越高,分得的請求越多
#========controller,負載均衡控制器========
worker.controller.type=lb
worker.controller.balanced_workers=tomcat1,tomcat2
#指定分擔請求的tomcat
worker.controller.sticky_session=1
2.2.5. 修改tomcat配置文件server.xml
如果你是水平集群,即在不同電腦上安裝tomcat,tomcat的安裝數量為一個,可以不必修改tomcat配置文件.我這里是在同一台電腦上安裝兩個tomcat,實現的是垂直集群方式,所以必須修改其中一個的設置,以避免端口沖突,按照參考文章是把原來以9開頭的端口號改為以9開頭端口號,但是在我機器上如果以9開頭的端口號,例如9080、9082會與我的WebSphere Application Server配置沖突,所以我這里采取的策略是把原來端口號的第三位改為1,如8080改為8180。
打開tomcat2/conf/server.xml文件
1) 將關閉Tomcat的監聽端口改成由8005改為8105
即把
<Server port="8005" shutdown="SHUTDOWN">
改為
<Server port="8105" shutdown="SHUTDOWN">
2) 把http服務端口號由8080改為8180
找到
<!-- Define a non-SSL HTTP/1.1 Connector on port 8080 -->
<CONNECTOR port="8080"
把這里的8080改為8180
3) 把AJP端口號由8009改為8109
找到
<!-- Define an AJP 1.3 Connector on port 8009 -->
<CONNECTOR port="8009"
把這里的8009改為8109
4) 把 HTTP 代理端口從8082改為8182(這個配置默認是被注釋掉的,可跳過這一步)
找到
<CONNECTOR port="8082"
把這里的8082改為8182
5) 編寫一個測試 jsp
建立一個目錄TestCluster,里面新建一個test.jsp,內容為
<% System.out.println("==========================="); %> |
把TestCluster放到tomcat1,tomcat2的webapps下
6) 啟動apache,tomcat1,tomcat2,進行測試
通過 http://localhost/TestCluster/test.jsp 訪問,多刷新幾次頁面,查看Tomcat1和Tomcat2的窗口,你將可以看到打印了一行行"===========================",並且從統計上來說,大約在tomcat2打印的數量是在Tomcat1中的兩倍,可以看到請求會被tomcat1,tomcat2按照不同的權重分流處理,實現了負載均衡。
作下面的集群配置,請在workers.properties把tomcat1和tomcat2的權重改為一樣的,使請求較平均分配,將有便於看到實驗的效果。
2.3. 配置集群
只配置負載均衡還不行,還要session復制,也就是說其中任何一個tomcat的添加的session,是要同步復制到其它tomcat, 集群內的tomcat都有相同的session
2.3.1. 修改tomcat1, tomcat2的server.xml,將集群部分配置,
即對<Cluster>節點的在注釋符刪掉,並將tomcat2的4001端口改為4002,以避免與tomcat沖突,當然,如果是兩台電腦,是不用改端口的,去掉注釋符即可
即取消對如下處
<Cluster className="org.apache.catalina.cluster.tcp.SimpleTcpCluster"
managerClassName="org.apache.catalina.cluster.session.DeltaManager"
expireSessionsOnShutdown="false"
............
<ClusterListener className="org.apache.catalina.cluster.session.ClusterSessionListener"/>
</Cluster>
前后的注釋標記<!-- -->,啟用該項配置,實現服務器間的Session復制。
2.3.2. 為 Tomcat1和 Tomcat2 增加 jvmRoute
這里的作用大概是為了在集群的TOMCAT里面,生成全局HTTP會話標識,這樣可以保證會話的唯一性,有利於session的復制
(先跳過這一步,有精力可以試驗一下)
在 Tomcat1 和 Tomcat2 的 server.xml 文件,找到
<ENGINE name="Catalina" defaultHost="localhost">
分別改為
<ENGINE name="Catalina" defaultHost="localhost" jvmRoute="tomcat1">
和
<ENGINE name="Catalina" defaultHost="localhost" jvmRoute="tomcat2">
然而實際我配置的時候還不能加jvmRoute屬性,配置了反而有問題。
刷新瀏覽器窗口總是在某一個tomcat控制台輸出形如
SessionID:154678FA6D4D0ABD57658B750E7A3532.tomcat1 (在tomcat1窗口)
或者
SessionID:3800571A532AECEA7280F45361861AD4.tomcat2 (在tomcat2窗口)
由控制台打印的結果可以看出,SessionID在哪個tomcat上產生,那么后續該會話的請求將總是會這個tomcat來處理。
並且注意到SessionID的形式比通常情況多了一個后綴.tomcat1或.tomcat2,還搞不清楚是為什么。
配置時請視實際情況而取舍。
2.3.3. 修改測試項目 TestCluster
修改test.jsp,內容如下
<%@ page contentType="text/html; charset=GBK" %> <%@ page import="java.util.*" %> <html><head><title>Cluster App Test</title></head> <body> <% System.out.println("SessionID:" + session.getId()); %> Server Info: <% out.println(request.getServerName() + " : " + request.getServerPort()+"<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.print("<b>Session 列表</b><br>"); Enumeration e = session.getAttributeNames(); while (e.hasMoreElements()) { String name = (String)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. 配置Session復制
在TestCluster目錄下新建WEB-INF目錄,WEB-INF下新建web.xml,內容如下
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">
<display-name>TomcatClusterDemo</display-name>
<distributable/>
<web-app>
也就是在需要集群的應用的web.xml中加上屬性,表明該應用可多應用分流處理,能進行Session的復制
把TestCluster復制到Tomcat1、Tomcat2的webapps目錄下,重啟apache,tomcat1,tomcat2
5. 測試Session的復制
通過 http://localhost/TestCluster/test.jsp 訪問,輸入名稱為 name, 值為 Unmi,提交查詢,多刷新幾次瀏覽器窗口,你將會看到在兩個Tomcat窗口都打印出相同的SessionID及其中的值,並且每次刷新后打印的結果都一樣的。
如果不為應用的web.xml加上 ,同樣測試上面那個test.jsp頁面,每次刷新分流到不同的tomcat上都會產生不一樣的SessionID,在同一個tomcat上也是間隔出現不同的sessionID。
更切身的體驗是一定要自己動手配置一遍,並仔細觀察兩個tomcat的控制上的輸出。因本文是參考 輕松實現Apache,Tomcat集群和負載均衡 的實踐經歷,
2.3.4. 測試session復制
啟動環境后,啟動2個瀏覽器,得到的是2個不同的session標識,各自提交您輸入的key-value后,把一個tomcat2服務關閉掉,再訪問頁面,發現tomcat2的session會復制到tomcat1中
2.3.5. 后記:
用 WebSphere Application Server ND 版配置過垂直和水平集群,但是自己試驗集群環境下的應用卻不想搬弄這個龐然大物。眼下急於想體驗的就是 Quartz 如何適應集群環境,問題的焦點就是:Quartz 定時任務隨 Web 應用啟動,而 Web 應用部署在集群環境中,如何保證同一時刻只有一個同名的任務實例在跑。
所以會考慮用Apache+Tomcat配置一個輕量級的WEB應用集群,一般進行HTTP分流都是使用Apache,包括WAS集群也是,很少用IIS的。雖然單純的用Tomcat的balancer應用也能配置進行負載分流,但那個性能應該好不到哪兒去。
用Apache+Tomcat配置的Web應用集群就是部署起來麻煩些,總是要保持雙份的應用拷貝,WAS集群則不需要,不知道Jboss做WEB應用集群是怎么樣一種情況。
好了,下面要進行該做的事情了,最后也希望能寫個工具能完成從下載到安裝配置,啟動,停止,重啟的全自動化,以及界面的人性化。
3. 實戰總結
3.1. 失敗總結
在准備軟件環境的環節中:最開始的時候小弟下載了Apache2.2.21,mod_jk下載的是2.2.X版本,沒有按照網上給出的版本,所有配置都弄好后,訪問頁面,總是提示404錯誤
后來換回2.0.55版本,這次實戰才成功了。
4. 參考文獻
4.1. 結合Apache和Tomcat實現集群和負載均衡
http://blog.csdn.net/kypfos/article/details/3081330
4.2. 揭開J2EE集群的神秘面紗
http://www.kuqin.com/java/20080418/6942.html