apache+Tomcat負載平衡設置詳解

作者: 來源:zz 發表時間:2006-08-11     瀏覽次數: 3208      字號:

一、簡介:
每個Tomcat worker是一個服務于web server、等待執行servlet的Tomcat實例。例如我們經常使用像Apache之類的web server轉發sevlet請求給位于其后面的一個Tomcat進程(也就是前面所說的worker)。本文詳細介紹了如何配置各種類型worker和loadbalance,并說明了各種類型worker的特性和loadbalance配置的原理。
二、為什么使用Tomcat workers:
上文描述了一個非常簡單的結構,事實上能夠配置多個Tomcat workers來處理web server轉發的servlet請求。而這樣配置的理由不外乎以下幾種假想環境:
* 我們在開發環境中發布不同的Tomcat workers為各自不同的應用服務。當然在開發環境中的開發者共享同一個web server,但是每個Tomcat worke服務于擁有它的開發者。
* 我們在不同的Tomcat進程上定義各自的虛擬主機,這樣不同的公司可以使用各自的web site,從而使他們的web site得到了合理的分割。
* 我們提供負載平衡的web site,也就意味著同時使用多個Tomcat workers,而每個Tomcat worker具有獨立的主機并且在workers之間要分配通過web server轉發來的請求。
當然,這些假想情況也許并不能涵蓋使用多個workers的所有狀況。
三、workers.properties配置說明:
定義Tomcat workers的方法是在apache的conf目錄下編寫一個名為“workers.properties”的屬性文件。本文將詳細解釋如何進行配置的:
1.定義Workers列表:
定義workers的方法就是在apache的conf目錄下編寫一個workers.properties文件,使其作為apache的插件來發揮作用。
定義workers列表的格式:
worker.list =<使用“,”分割的worker 名字列表>
例如:


worker.list= worker1, worker2

當apache啟動時,workers.properties作為插件將初始化出現在worker.list列表中的workers。
2.定義Workers的類型:
每個被命名的worker都應有一些關于其自身的附加信息。這些信息包括了worker的類型和其它相關信息。這里討論的是JK1.2.5中定義的workers類型。
定義worker類型的格式:
worker . worker名字. type =<worker類型>
worker名字的命名最好遵循java的命名規范。
worker類型取值于下面的表格:
定義一個名為“local”的worker,其使用ajpv12協議與Tomcat 進程通訊:

worker.local.type=ajp12

定義一個名為“remote”的worker,其使用ajpv13協議與Tomcat 進程通訊:

worker.remote.type=ajp13

定義一個名為“fast”的worker,其使用JNI的方式與Tomcat 進程通訊:

worker.fast.type=jni

定義一個名為“loadbalancer”的worker,其作為對多個Tomcat 進程的負載平衡使用:

worker.loadbalancer.type=lb

各個類型具有不同的行為,我們在下文中會詳細解釋。
3.設置Worker屬性:
在定義worker之后,還需要提供各個worker的屬性,這些屬性的定義使用下面的方式:
worker.<worker名字>.<屬性>=<屬性值>
3-1 ajp12類型的Worker屬性:.
ajp12類型的worker工作時使用基于TCP/IP socket的ajpv12協議轉發請求給“進程外”Tomcat worker。
ajp12 worker屬性如下:
host:
偵聽ajp12請求的Tomcat worker主機。
port:
Tomcat worker主機的偵聽端口。
lbfactor:
當此Tomcat worker被用于一個負載平衡worker使用時,此屬性將被使用。它定義了此worker的負載平衡權值。
例如:下面的“worker1”定義了一個位于www.x.com主機上的Tomcat,它使用8007端口偵聽apache發來的請求,并具有2.5的負載權值

worker.worker1.host=www.x.com
worker.worker1.port=8007
worker.worker1.lbfactor=2.5

注意:在ajpv12協議中,針對每個請求都要一個連接建立、使用、關閉。其默認偵聽端口為8007。
3-2 ajp13類型的Worker屬性:
ajp13類型的worker工作時使用基于TCP/IP socket的ajpv13協議轉發請求給“進程外”Tomcat worker。
ajpv13協議與ajpv12協議的主要不同:
* ajpv13具有更豐富的二進制協議,它使用將頻繁使用的字符串編碼為小整數的方式對請求數據進行壓縮。
* ajpv13重用打開的socket并保留這些打開的socket以處理將來的請求。這在apache與Tomcat之間具有防火墻的網絡環境下是必要的。
* ajpv13具有對SSL信息的處理能力,以致容器能夠實現SSL的相關方法(如isSecure())。
注意:ajp13當前只能用于支持“進程外”協議的Tomcat 4.0.x, 4.1.x and 5。
下表描述了ajp13worker接受的屬性:
host:
偵聽ajp13請求的Tomcat worker主機。
port:
Tomcat worker主機的偵聽端口。
lbfactor:
當此Tomcat worker被用于一個負載平衡worker使用時,此屬性將被使用。它定義了此worker的負載平衡權值。
cachesize:
當在多線程的web server(例如apache2.0、IIS 、Netscape)中使用JK時,此屬性是有效的。如果將cachesize的值設置為較高的值,這些支持多線程的web server將獲得很好的處理能力。如果此屬性不被設置,則連接cache特性將失效。
cache_timeout:
本屬性用于聲明JK在cache中保留一個打開的socket的時間,它對減少web serer的線程數有所幫助。
使用cache_timeout的原因:
周所周知,一個身背重負的web server(例如apache)建立childs/threads來處理負載,而當負載減少時它將銷毀無用的childs/threads。每個child在轉發請求給Tomcat時要打開一個ajp13連接,而在Tomcat那一端也將建立一個ajp13線程與之通訊。但是問題出現在一個ajp13連接建立完成后,child沒有及時的釋放那個ajp13連接,由于web server1將保持它的childs/threads運行已處理高負載,即使childs/threads處理快速的靜態內容,在Tomcat端也將積累很多的無用ajp13線程。
socket_keepalive:
當防火墻位于web server與Tomcat之間時,防火墻將嘗試斷開未激活的網絡連接。此屬性將告訴操作系統在未激活的連接中發送KEEP_ALIVE信息(發送間隔時間依賴于操作系統的設置,一般為120秒),這樣將防止防火墻切斷未激活的網絡連接。
但此設置并不是萬能鑰匙,它對于某些防火墻也無能為力。
socket_timeout:
此屬性說明連接在未激活的狀況下持續多久,web server將主動切斷之。這是一個使Tomcat端的陳舊線程不致過多的好方法,但是也帶來了在下一次請求到來時需要重新打開socket的開銷。此屬性與cache_timeout有類似的功效,但是它工作在non-cache模式。
connect_timeout:
web server在連接建立后將一個PING請求發送到ajp13協議的連接上。 此屬性說明了web server等待PONG回應的時間(以ms為單位)。此屬性在jk 1.2.6版本被增加進來,以求避免Tomcat的死機,Tomcat 3.3.2+, 4.1.28+ and 5.0.13+實現了對使用ajp13的 ping/pong的支持。此屬性默認為失效的。
prepost_timeout:
web server在轉發一個請求后將一個PING請求發送到ajp13協議的連接上。此屬性說明了web server等待PONG回應的時間(以ms為單位)。此屬性在jk 1.2.6版本被增加進來,以求避免Tomcat的死機,Tomcat 3.3.2+, 4.1.28+ and 5.0.13+實現了對使用ajp13的 ping/pong的支持。此屬性默認為失效的。
reply_timeout:
此屬性告訴web server在接到遠端的Tomcat已死并實時的切換到集群中的另外一個Tomcat的回應之前等待一段時間。默認情況下web server將永遠等待。屬性值為web server要等待回應的時間(以ms為單位),所以如果具有運行時間較長的servlet時設置其值要小心。此屬性在jk 1.2.6版本被增加進來,以求避免Tomcat的死機和在支持ajp13的servlet引擎上發生的問題。此屬性默認為失效的。
recovery_options:
此屬性說明了web server在檢測到Tomcat失敗后如何進行恢復工作。默認情況下,web server將轉發請求給處于負載平衡模式中的另一個Tomcat。屬性值為0,說明全部恢復;屬性值為1,說明如果在Tomcat接到請求后出現失敗狀況,則不進行恢復;屬性值為2,說明如果在Tomcat發送http頭給客戶端后出現失敗狀況,則不進行恢復;屬性值為3,說明如果在Tomcat接到請求后出現失敗狀況或者在Tomcat發送http頭給客戶端后出現失敗狀況,則不進行恢復。此屬性在jk 1.2.6版本被增加進來,以求避免Tomcat的死機和在支持ajp13的servlet引擎上發生的問題。此屬性默認為全部恢復。
例如:一個名為“worker2”的worker的配置:

worker.worker2.host=www2.x.com
worker.worker2.port=8009
worker.worker2.lbfactor=3.5
worker.worker2.cachesize=10
worker.worker2.cache_timeout=600
worker.worker2.socket_keepalive=1
worker "worker2" want ajp13 connection to be dropped after 5mn (timeout)
worker.worker2.socket_timeout=300

說明:上例中的worker要求操作系統在連接上發送KEEP-ALIVE信號。
注意:在ajpv13協議中默認端口為8009。
4.設置lb Worker屬性:
負載平衡類型的worker并不與Tomcat worker通訊,它負責管理這些Tomcat worker。
其管理范圍如下:
* 初始化在web server的worker列表中定義的worker。
* 使用worker的負載平衡權值,執行基于權值的負載平衡,將數量多的請求發送到負載平衡權值高(在web server看來就是更加健壯的)的worker。
* 維護在同一個Tomcat worker上的同一個session的請求,使其發送到同一個Tomcat worker上。以達到Tomcat worker上的session一致性、持續性。
* 標識已經失敗的Tomcat workers,懸空發向它們的請求,在被lb worker管理的其它workers上尋找可以失敗恢復的worker。
被同一個lb worker管理多個worker之間的負載平衡的(基于它們的lbfactor和當前用戶session),也可以盡量避免由于單一的Tomcat進程死掉而造成這個網站被“殺”的不良反應。
下表說明了lb worker接受的屬性:
* balanced_workers:一個由“,”分割的worker列表,用來聲明lb worker需要被管理的workers。這些workers不應出現在worker.list屬性中。
* sticky_session:表述是否將對SESSION ID的請求路由回到相同的Tomcat worker。如果屬性值不為0,它將被設置為JK_TRUE,session將是粘性的,即SESSION ID的請求路由回到相同的Tomcat worker;當Tomcat正使用能夠跨越多個Tomcat實例持久化session數據的Session Manager時,它將被設置為JK_FALSE。屬性默認值為JK_TRUE。
例如:worker balance1管理著兩個workers:worker1、worker2:

worker.balance1.balanced_workers=worker1, worker2

5.高級lb Worker屬性:
JK 1.2.x版本通過增加兩個新的屬性:local_worker_only 和 local_worker 為lb worker增添了新的負載平衡和容錯支持。
下面讓我們舉一個實際的環境作為example:
一個集群具有兩個節點(worker1+worker2),一個web server與tomcat workers一前一后,一個負載平衡器(lb Worker)位于節點的前面、web server的后面。
配置如下:

worker.list=router
# Define a 'local_worker' worker using ajp13
worker.worker1.port=8009
worker.worker1.host=node1.domain.org
worker.worker1.type=ajp13
worker.worker1.lbfactor=1
worker.worker1.local_worker=1
# Define another 'local_worker' worker using ajp13
worker.worker2.port=8009
worker.worker2.host=node2.domain.org
worker.worker2.type=ajp13
worker.worker2.lbfactor=1
worker.worker2.local_worker=0
# Define the LB worker
worker.router.type=lb
worker.router.balanced_workers=worker1,worker2
worker.router.local_worker_only=1

在worker1和worker2上的local_worker標志告訴lb_worker哪個連接屬于本地worker。
如果local_worker值為非0,則它將被設置為JK_TRUE,用來標記“local worker”,而JK_FALSE的情況則相反。如果至少一個worker被標記為local worker,則lb_worker將工作于local worker模式。這種模式下,所有的local workers將被移到在lb_worker中的內部worker列表的頭部。
這意味著一個帶有session id的請求到達lb_worker時,相應的worker(根據權值排序,權值最大的那個worker)將被確定作為此請求的接受/處理者。如果這個worker死掉/當機,請求將被發送到處于非錯誤狀態的第一個local worker;如果一個沒有session id的請求到達lb_worker時,此請求將被路由到第一個local worker。如果所有的local worker均處于錯誤狀態,則這時“local_worker_only”標志顯得尤其重要。如果local_worker_only的屬性值為非0,則它被設置為 JK_TRUE,否則被設置為 JK_FALSE。當它被設置為 JK_TRUE時,這個沒有session id的請求將得到一個錯誤作為回應,否則lb_worker將嘗試將請求路由到其它的被管理的worker上。如果其中的一個worker處于錯誤狀態,并且恢復會話的工作并沒有任何改變,local worker將查找這個沒有session id的請求(因為在local worker中保存有這個請求的session),而其它的worker只能查找帶有session id的請求。
注意:local_worker默認值是0,local_worker_only默認值也是0。
6.為什么需要這么復雜的過程嗎?
因為我們對于一個關閉的節點需要一個具有靈性的維護。
在節點前面的平衡器周期性的對每個節點的特定端口進行查詢。如果我們從集群中移走一個節點,我們就會隱性的關閉掉這個特定的端口。由于負載平衡器不能連接它,這個節點將被標記為down。但是我們沒有移動在那個關閉的節點上的session到其它的節點上。在這個環境下,如果平衡器發送一個沒有session id的請求到一個端口被關掉的節點,那么一個錯誤將發生。如果平衡器測試到一個節點被標記為down的狀態,而沒有其它的節點允許發送沒有session id的請求。這樣這些陳舊的session請求就只有路由到那個被關閉的節點才能被接受。在一段時間后,這些陳舊的session將超時。由于所有的陳舊的session過期,那個不可達(被關閉)的節點將失去這個請求。同時也會導致我們的servlet系統發送一個沒有session id的重定向回應給瀏覽器。
但是可能被關閉的節點將會up,重新加入到集群中來,在它上面仍將保留著陳舊的session。所以在最后一個session超時后,更新節點能夠為陳舊的session的恢復帶來希望,而不是殺掉sessions或者把它們移到其它節點上。而且有時如果那些陳舊的session中有許多big的對象,那么移動它們也將花費許多時間。
7.jni類型的Worker屬性:
jni worker會在web server進程中打開一個JVM,并在其中執行Tomcat,這叫做“進程內”worker。來往于JVM的消息將通過調用JNI方法被傳遞,這使jni worker比那些需要使用ajp消息通訊的“進程外”worker執行的更快。
注意:由于JVM是多線程的,jni worker應該只被用于在支持對線程的web server(AOLServer, IIS, Netscape and Apache 2.0)上。同時還應該確認在web server上使用的線程方案是否與被使用的JK web server插件相匹配。
由于jni worker 打開了一個JVM,它將接受一些屬性(例如classpath等)并將其傳遞給JVM:
worker.worker名.class_path:“進程內”的JVM要使用的classpath。它將包括所有的Tomcat的jar文件和class、配置文件等。
為了獲得JSP編譯器的支持,我們需要將Javac添加到classpath中。當然對于Java2需要添加tools.jar到classpath,而對于JDK1.xx則要添加classes.zip到classpath。
worker.worker名.class_path:用于以多行的形式聲明多個classpath。JK環境將用“:”或者“;”把這些classpath;連接起來。
例如:給名為“wrkjni”的worker設置classpath。

worker.wrkjni.class_path=/var/tomcat3/lib/tomcat.jar
worker.wrkjni.class_path=/opt/IBMJava2-131/lib/tools.jar

worker.worker名.bridge:用于標識將通過JNI方式被使用的Tomcat的類型。此屬性目前有兩個屬性值:tomcat32 or tomcat33。Tomcat 3.2.x雖然已經過時,但是被提供用于發布在一些類似iSeries系統上。此屬性的默認值為tomcat33。
例如:給“wrkjni”設置bridge類型為tomcat3.3。

worker.wrkjni.bridge=tomcat33

worker.worker名.cmd_line: 此屬性提供了在Tomcat啟動代碼執行的命令行。使用時將命令行的命令、參數分解為多個cmd_line屬性。JK環境通過在各個cmd_line屬性值之間添加空格將這些cmd_line連接在一起。
例如:設置“wrkjni”的cmd_line屬性。

worker.wrkjni.cmd_line=-config
worker.wrkjni.cmd_line=/etc/tomcat3/conf/alt-server.xml
worker.wrkjni.cmd_line=-home
worker.wrkjni.cmd_line=/var/tomcat3

上面例子中的第一行聲明了-config參數名,而第二行聲明了與之對應的參數值。第三行與第四行同理。
worker.worker名.jvm_lib:用于聲明JVM的實現庫的完整路徑。Jni worker使用這個路徑動態裝載JVM。
例如:設置“wrkjni”的JVM shared lib (IBM SDK on Linux)。

worker.wrkjni.jvm_lib=/opt/IBMJava2-131/jre/bin/classic/libjvm.so

例如:設置“wrkjni”的JVM shared lib (Sun SDK on Windows)。

worker.wrkjni.jvm_lib=c:\JDK\1.3.1\jre\bin\classic

worker.worker名.stdout:設置JVM寫它的System.out的完整路徑位置。
例如:將“wrkjni”的JVM系統輸出路徑設置為/var/log/http/jk-jvm-out.log。

worker.wrkjni.stdout=/var/log/http/jk-jvm-out.log

worker.worker名.stderr:設置JVM寫它的System.err的完整路徑位置。
例如:將“wrkjni”的JVM系統錯誤輸出路徑設置為/var/log/http/jk-jvm-err.log

worker.wrkjni.stderr=/var/log/http/jk-jvm-out.log

worker.worker名.ms:設置JVM的初始堆大小。
例如:設置“wrkjni”的JVM的初始堆為64M。

worker.wrkjni.ms=64

worker.worker名.mx:設置JVM的最大的堆大小。
例如:設置“wrkjni”的JVM堆最大為128M

worker.wrkjni.mx=128

worker.worker名.sysprops:設置JVM的系統屬性。
例如:設置“wrkjni”的JVM使用法語。

worker.wrkjni.sysprops=-Duser.region=FR

worker.worker名.ld_path:設置附加的動態鏈接庫路徑(類似于LD_LIBRARY_PATH)
例如:添加一些動態鏈接庫路徑到“wrkjni”的java環境中。

worker.wrkjni.ld_path=/opt/IBMJava2-131/jre/bin/
worker.wrkjni.ld_path=/opt/IBMJava2-131/jre/bin/classic

注意:在Linux下,上面的ld_path并不能更新LD_LIBRARY_PATH,所以需要在執行web server之前手動更新LD_LIBRARY_PATH,。
8.屬性文件宏:
我們可以在屬性文件中定義“宏”。這些宏讓我們定義屬性,并在以后使用它們來構建其它的屬性文件。當我們修改Java Home、Tomcat Home、系統路徑分隔符時這是很有用的。
例如:定義了屬性workers.tomcat_home、workers.java_home。

workers.tomcat_home=d:\tomcat
workers.java_home=d:\sdk\jdk1.2.2

在定義worker.inprocess.class_path時就可以使用前面定義的workers.tomcat_home。

worker.inprocess.class_path=$(workers.tomcat_home)$(ps)classes

9.一個簡單而完整的worker.properties:
文件中定義了比較完整的結構,可以做為參考模版:
* 一個位于localhost的使用8007端口的ajp12 worker;
    * 一個位于localhost的使用8008端口的ajp13 worker;
    * 一個jni worker;
    *  一個lb worker:負責ajp12 worker、ajp13 workers的負載平衡。
文件內容如下:

# Define some properties
workers.apache_log=/var/log/httpd/
workers.tomcat_home=/var/tomcat3
workers.java_home=/opt/IBMJava2-131/
ps=/
# Define 4 workers, 3 real workers using ajp12, ajp13, jni, the last one being a loadbalancing worker
worker.list=worker1, worker2, worker3, worker4
# Set properties for worker1 (ajp12)
worker.worker1.type=ajp12
worker.worker1.host=locahost
worker.worker1.port=8007
worker.worker1.lbfactor=5
# Set properties for worker2 (ajp13)
worker.worker2.type=ajp13
worker.worker2.host=locahost
worker.worker2.port=8009
worker.worker2.lbfactor=50
worker.worker2.cachesize=10
worker.worker2.cache_timeout=600
worker.worker2.socket_keepalive=1
worker.worker2.socket_timeout=300
# Set properties for worker3 (jni)
worker.worker3.type=jni
# Set worker3 bridge type, here Tomcat 3.3
worker.worker3.bridge=tomcat33
# Set worker3 classpath
worker.worker3.class_path=$(workers.tomcat_home)$(ps)classes
worker.worker3.class_path=$(workers.tomcat_home)$(ps)lib$(ps)tomcat.jar
# Set worker3 tomcat command line
worker.worker3.cmd_line=-home
worker.worker3.cmd_line=$(workers.tomcat_home)
# Set worker3 Tomcat/JVM settings
worker.worker3.jvm_lib=$(workers.java_home)$(ps)jre$(ps)bin$(ps)classic$(ps)libjvm.so
worker.worker3.stdout=$(workers.apache_log)$(ps)inprocess.stdout
worker.worker3.stderr=$(workers.apache_log)$(ps)inprocess.stderr
worker.worker3.sysprops=tomcat.home=$(workers.tomcat_home)
# Set properties for worker4 (lb) which use worker1 and worker2
worker.worker4.balanced_workers=worker1,worker2