詳談tomcat集群
一、為何要集群
單台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(其實無所謂順序的),來看效果:

分別訪問https://localhost:8080/cbbs與https://localhost:9090/cbbs
確保兩個tomcat節點都起來了,然後此時,我們啟動Apache
然後訪問直接用https://localhost/cbbs不加端口的形式訪問:

用sally/abcdefg登錄,瞧,應用起來了。
然後我們拿另一台物理客戶端,登錄這個web應用,我們可以看到:

第一個tomcat正在負責處理我們第一次登錄的請求。
當有第二個HTTP請求時,另一個tomcat自動開始肩負起我們第二個HTTP請求了,這就是Load Balance。
最後更新:2017-04-02 16:48:11
上一篇:
JBOSS基本安裝以及端口的修改
下一篇:
tomcat中conf\Catalina\localhost目錄下的J2EE項目META-INF配置文件
探索宇宙終極問題 國家天文台與阿裏雲加深合作
容器就像監獄,讓我們來構造一個監獄吧!(含代碼下載)
Httpclient核心架構設計
MacRuby 0.3發布,支持Interface Builder,和創建GUI用的HotCocoa
keepalived是如何實現MySQL高可用的?
CareerCup之1.2C風格字符串翻轉
Java中IO流緩衝區的裝飾模式的體現
PostgreSQL教程(二):SQL語言
金融安全資訊精選 2017年第七期:Equifax 泄漏 1.43 億用戶數據,Struts2 REST插件遠程執行命令漏洞全麵分析,阿裏雲護航金磚五國大會
競價推廣賬戶日常優化需要注意十大要點