一、爲何要集羣
單臺App Server再強勁,也有其瓶勁,先來看一下下面這個真實的場景。
當時這個工程是這樣的,tomcat這一段被稱爲web zone,裏面用spring+ws,還裝了一個jboss的規則引擎Guvnor5.x,全部是ws沒有service layer也沒有dao layer。
然後App Zone這邊是weblogic,傳輸用的是spring rmi,然後App Zone這塊全部是service layer, dao layer和數據庫打交道。
用戶這邊用的是.net,以ws和web zone連的。
時間一長,數據一多,就出問題了。
拿Loader Runner跑下來,發覺是Web Zone這塊,App Server已經被用到極限了。因爲客戶錢不多,所以當時的Web Zone是2臺服務器,且都是32位的,內存不少,有8GB,測試下來後發覺cpu loader又不高,但是web server這邊的吞吐量始終上不去,且和.net客戶端那邊響應越來越慢。
分析了一下原因:單臺tomcat能夠承受的最大負載已經到頭了,單臺tomcat的吞吐量就這麼點,還要負擔Guvnor的運行,Guvnor內有數百條業務規則要執行。
再看了一下其它方面的代碼、SQL調優都已經到了極限了,所以最後沒辦法,客戶又不肯拿錢投在內存和新機器上或者是再買臺Weblogic,只能取捨一下,搞Tomcat集羣了。
二、集羣分類
Tomcat作集羣的邏輯架構是上面這樣的一張圖,關鍵是我們的production環境還需要規劃好我們的物理架構。
2.1 橫向集羣
比如說,有兩臺Tomcat,分別運行在2臺物理機上,好處是最大的即CPU擴展,內存也擴展了,處理能力也擴展了。
2.2 縱向集羣
即,兩個Tomcat的實例運行在一臺物理器上,充分利用原有內存,CPU未得到擴展。
2.3 橫向還是縱向
一般來說,廣爲人們接受的是橫向擴展的集羣,可做大規模集羣佈署。但是我們這個case受制於客戶即:
ü 不會再投入新機器了
ü 不會增加內存了
但是呢,通過壓力測試報告我們可知:
ü 原有TomcatServer的CPU Loader不高,在23%左右
ü 原有TomcatServer上有8GB內存,而且是32位的,單臺Tomcat只使用了1800MB左右的內存
ü 網絡流量不高,單塊千兆以太網卡完全可以處理掉
因此,我們只能做熊掌與魚不能兼得的事,即採用了:縱向集羣。
2.4 Load Balance與High Available
ü Load Balance
簡稱LB即負載均衡,相當於1000根線程每個集羣節點:Node負責處理500個,這樣的效率是最高的。
ü High Available
簡稱HA即高可用性,相當於1000根線程還是交給一臺機器去慢慢處理,如果這臺機器崩了,另一臺機器頂上。
三、集羣架構中需要解決的問題
集羣規劃好了怎麼分,這不等於就可以開始實現集羣了,一旦你的系統實現了集羣,隨之而來的問題就會出現了。
我們原有系統中有這樣幾個問題,在集羣環境中是需要解決的,來看:
3.1 解決上傳文件同步的問題
集羣后就是兩個Tomcat了,即和兩個線程讀同一個resource的問題是一樣的,還好,我們原有上傳文件是專門有一臺文件伺服器的,這個問題不大,兩個tomcat都往一臺file server裏上傳,文件伺服器已經幫我們解決了同名文件衝突的這個問題了,如果原先的做法是把文件上傳到Tomcat的目錄中,那問題就大了,來看:
集羣環境中,對於用戶來說一切操作都是透明的,他也不知道我有幾個Tomcat的實例運行在那邊。
用戶一點上傳,可能上傳到了Tomcat2中,但是下次要顯示這個文件時,可能用到的是Tomcat1內的jsp或者是class,對不對?
於是,因爲你把圖片存在了Tomcat的目錄中,因此導致了Tomcat1在顯示圖片時,取不到Tomcat2目錄中存放的圖片。
因此我們在工程一開始就強調存圖片時要用一臺專門的文件服務器或者是FTP服務器來存,就是爲了避免將來出現這樣的問題。
3.2 解決Quartz在集羣環境中的同步問題
我們的系統用到一個Quartz(一個定時服務組件)來定時觸發一些自動機制,現在有了兩個Tomcat,粗想想每個Tomcat裏運行自己的Quartz不就行了?
但是問題來了,如果兩個Quartz在同一時間都觸發了處理同一條定單,即該條定單會被處理兩邊。。。這不是影響效率和增加出錯機率了嗎?
因爲本身Quartz所承受的壓力幾乎可以忽略不計的,它只是定時會觸發腳本去運行,關鍵在於這個定時腳本的同步性,一致性的問題上。
我們曾想過的解決方法:
我們可以讓一個Tomcat佈署Quartz,另一個Tomcat裏不佈署Quartz
但這樣做的結果就是如果佈署Quartz的這個Tomcat崩潰掉了,這個Quartz是不是也崩啦?
最後解決的辦法:
所以我們還是必須在兩臺Tomcat里布署Quartz,然後使用HA的原則,即一個Quartz在運行時,另一臺Quartz在監視着,並且不斷的和另一個Quartz之間保持勾通,一旦運行着的Quartz崩掉了,另一個Quartz在指定的秒數內起來接替原有的Quartz繼續運行,對於Quartz,我們同樣也是面臨着一個熊掌與魚不能皆得的問題了,Quartz本身是支持集羣的,而它支持的集羣方式正是HA,和我們想的是一致的。
具體Quartz是如何在集羣環境下作佈署的,請見我的另一篇文章:quartz在集羣環境下的最終解決方案
解決了上述的問題後基本我們可以開始佈署Tomcat這個集羣了。
四、佈署Tomcat集羣
準備兩個版本一致的Tomcat,分別起名爲tomcat1,tomcat2。
4.1 Apache中的配置
² worker.properties文件內容的修改
打開Apache HttpServer中的apache安裝目錄/conf/work.properties文件,大家還記得這個文件嗎?
這是原有文件內容:
workers.tomcat_home=d:/tomcat2 workers.java_home=C:/jdk1.6.32 ps=/ worker.list=ajp13 worker.ajp13.port=8009 worker.ajp13.host=localhost worker.ajp13.type=ajp13 |
現在開始改動成下面這樣的內容(把原有的worker.properties中的內容前面都加上#註釋掉):
#workers.tomcat_home=d:/tomcat2 #workers.java_home=C:/jdk1.6.32 #ps=/ #worker.list=ajp13 #worker.ajp13.port=8009 #worker.ajp13.host=localhost #worker.ajp13.type=ajp13 worker.list = controller #tomcat1 worker.tomcat1.port=8009 worker.tomcat1.host=localhost worker.tomcat1.type=ajp13 worker.tomcat1.lbfactor=1 #tomcat2 worker.tomcat2.port=9009 worker.tomcat2.host=localhost worker.tomcat2.type=ajp13 worker.tomcat2.lbfactor=1 #========controller======== worker.controller.type=lb worker.controller.balance_workers=tomcat1,tomcat2 worker.lbcontroller.sticky_session=0 worker.controller.sticky_session_force=true worker.connection_pool_size=3000 worker.connection_pool_minsize=50 worker.connection_pool_timeout=50000
|
上面的這些設置的意思用中文來表達就是:
ü 兩個tomcat,都位於localhost
ü 兩個tomcat,tomcat1用8009,tomcat2用9009與apache保持jk_mod的通訊
ü 不採用sticky_session的機制
sticky_session即:假設現在用戶正連着tomcat1,而tomcat1崩了,那麼此時它的session應該被複制到tomcat2上,由tomcat2繼續負責該用戶的操作,這就是load balance,此時這個用戶因該可以繼續操作。
如果你的sticky_session設成了1,那麼當你連的這臺tomcat崩了後,你的操作因爲是sticky(粘)住被指定的集羣節點的,因此你的session是不會被複制和同步到另一個還存活着的tomcat節點上的。
ü 兩臺tomcat被分派到的任務的權重(lbfactor)爲一致
你也可以設tomcat1 的worker.tomcat2.lbfactor=10,而tomcat2的worker.tomcat2.lbfactor=2,這個值越高,該tomcat節點被分派到的任務數就越多
² httpd.conf文件內容的修改
找到下面這一行:
Include conf/extra/httpd-ssl.conf |
我們將它註釋掉,因爲我們在集羣環境中不打算採用https,如果採用是https也一樣,只是爲了減省開銷(很多人都是用自己的開發電腦在做實驗哦)。
#Include conf/extra/httpd-ssl.conf |
找到原來的“<VirtualHost>”段
改成如下形式:
<VirtualHost *> DocumentRoot d:/www <Directory "d:/www/cbbs"> AllowOverride None Order allow,deny Allow from all </Directory> <Directory "d:/www/cbbs/WEB-INF"> Order deny,allow Deny from all </Directory> ServerAdmin localhost DocumentRoot d:/www/ ServerName shnlap93:80 DirectoryIndex index.html index.htm index.jsp index.action ErrorLog logs/shsc-error_log.txt CustomLog logs/shsc-access_log.txt common
JkMount /*WEB-INF controller JkMount /*j_spring_security_check controller JkMount /*.action controller JkMount /servlet/* controller JkMount /*.jsp controller JkMount /*.do controller JkMount /*.action controller
JkMount /*fckeditor/editor/filemanager/connectors/*.* controller JkMount /fckeditor/editor/filemanager/connectors/* controller </VirtualHost> |
注意:
原來的JKMount *** 後的 ajp13變成了什麼了?
controller
4.2 tomcat中的配置
可以拿原有的tomcat複製成另一個tomcat,分別爲d:\tomcat, d:\tomcat2。
打開tomcat中的conf目錄中的server.xml,找到下面這行
1)
<Server port="8005" shutdown="SHUTDOWN"> |
記得:
一定要把tomcat2中的這邊的”SHUTDOWN”的port改成另一個端口號,兩個tomcat如果是在集羣環境中,此處的端口號絕不能一樣。
2)找到
<Connector port="8080" protocol="HTTP/1.1" |
確保tomcat2中此處的端口不能爲8080,我們就使用9090這個端口吧
3)把兩個tomcat中原有的https的配置,整段去除
4)找到
<Connector port="8080" protocol="HTTP/1.1" URIEncoding="UTF-8" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" disableUploadTimeout="true" connectionTimeout="20000" acceptCount="300" maxThreads="300" maxProcessors="1000" minProcessors="5" useURIValidationHack="false" compression="on" compressionMinSize="2048" compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain" redirectPort="8443" /> |
確保tomcat2中這邊的redirectPort爲9443
5)找到
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" |
改爲:
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" URIEncoding="UTF-8" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" disableUploadTimeout="true" connectionTimeout="20000" acceptCount="300" maxThreads="300" maxProcessors="1000" minProcessors="5" useURIValidationHack="false" compression="on" compressionMinSize="2048" compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain"
/> |
確保tomcat2的server.xml中此處的8009被改成了9009且其它內容與上述內容一致(redirectPort不要忘了改成9443)
6)找到
<Engine name="Standalone" defaultHost="localhost" jvmRoute="jvm1"> |
改成
<!-- You should set jvmRoute to support load-balancing via AJP ie : <Engine name="Standalone" defaultHost="localhost" jvmRoute="jvm1"> --> <Engine name="Standalone" defaultHost="localhost" jvmRoute="tomcat1"> |
同時把tomcat2中此處內容改成
<!-- You should set jvmRoute to support load-balancing via AJP ie : <Engine name="Standalone" defaultHost="localhost" jvmRoute="jvm1"> --> <Engine name="Standalone" defaultHost="localhost" jvmRoute="tomcat2"> |
7)
在剛纔的
<Engine name="Standalone" defaultHost="localhost" jvmRoute="tomcat1"> |
的下面與在
<!-- The request dumper valve dumps useful debugging information about the request and response data received and sent by Tomcat. Documentation at: /docs/config/valve.html --> <!-- <Valve className="org.apache.catalina.valves.RequestDumperValve"/> --> |
之上,在這之間加入如下一大陀的東西:
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="6"> <Manager className="org.apache.catalina.ha.session.BackupManager" expireSessionsOnShutdown="false" notifyListenersOnReplication="true" mapSendOptions="6"/> <Channel className="org.apache.catalina.tribes.group.GroupChannel"> <Membership className="org.apache.catalina.tribes.membership.McastService" bind="127.0.0.1" address="228.0.0.4" port="45564" frequency="500" dropTime="3000"/> <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver" address="auto" port="4001" selectorTimeout="100" maxThreads="6"/> <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter"> <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender" timeout="60000"/> </Sender> <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/> <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/> <Interceptor className="org.apache.catalina.tribes.group.interceptors.ThroughputInterceptor"/> </Channel> <Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter=".*\.gif;.*\.js;.*\.jpg;.*\.png;.*\.htm;.*\.html;.*\.css;.*\.txt;"/> <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/> </Cluster> |
此處有一個Receiver port=”xxxx”,兩個tomcat中此處的端口號必須唯一,即tomcat中我們使用的是port=4001,那麼我們在tomcat2中將使用port=4002
8)把系統環境變更中的CATALINA_HOME與TOMCAT_HOME這兩個變量去除掉
9)在每個tomcat的webapps目錄下佈署同樣的一個工程,在佈署工程前先確保你把工程中的WEB-INF\we b.xml文件做了如下的修改,在web.xml文件的最未尾即“</web-app>”這一行前加入如下的一行:
<distributable/> |
使該工程中的session可以被tomcat的集羣節點進行輪循複製。
4.3 啓動集羣
好了,現在啓動tomcat1, 啓動tomcat2(其實無所謂順序的),來看效果:
分別訪問http://localhost:8080/cbbs與http://localhost:9090/cbbs
確保兩個tomcat節點都起來了,然後此時,我們啓動Apache
然後訪問直接用http://localhost/cbbs不加端口的形式訪問:
用sally/abcdefg登錄,瞧,應用起來了。
然後我們拿另一臺物理客戶端,登錄這個web應用,我們可以看到:
第一個tomcat正在負責處理我們第一次登錄的請求。
當有第二個HTTP請求時,另一個tomcat自動開始肩負起我們第二個HTTP請求了,這就是Load Balance。