單台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環境還需要規劃好我們的實體架構。
比如說,有兩台Tomcat,分别運作在2台實體機上,好處是最大的即CPU擴充,記憶體也擴充了,處理能力也擴充了。
即,兩個Tomcat的執行個體運作在一台實體器上,充分利用原有記憶體,CPU未得到擴充。
一般來說,廣為人們接受的是橫向擴充的叢集,可做大規模叢集布署。但是我們這個case受制于客戶即:
ü 不會再投入新機器了
ü 不會增加記憶體了
但是呢,通過壓力測試報告我們可知:
ü 原有TomcatServer的CPU Loader不高,在23%左右
ü 原有TomcatServer上有8GB記憶體,而且是32位的,單台Tomcat隻使用了1800MB左右的記憶體
ü 網絡流量不高,單塊千兆以太網卡完全可以處理掉
是以,我們隻能做熊掌與魚不能兼得的事,即采用了:縱向叢集。
ü Load Balance
簡稱LB即負載均衡,相當于1000根線程每個叢集節點:Node負責處理500個,這樣的效率是最高的。
ü High Available
簡稱HA即高可用性,相當于1000根線程還是交給一台機器去慢慢處理,如果這台機器崩了,另一台機器頂上。
叢集規劃好了怎麼分,這不等于就可以開始實作叢集了,一旦你的系統實作了叢集,随之而來的問題就會出現了。
我們原有系統中有這樣幾個問題,在叢集環境中是需要解決的,來看:
叢集後就是兩個Tomcat了,即和兩個線程讀同一個resource的問題是一樣的,還好,我們原有上傳檔案是專門有一台檔案伺服器的,這個問題不大,兩個tomcat都往一台file server裡上傳,檔案伺服器已經幫我們解決了同名檔案沖突的這個問題了,如果原先的做法是把檔案上傳到Tomcat的目錄中,那問題就大了,來看:
叢集環境中,對于使用者來說一切操作都是透明的,他也不知道我有幾個Tomcat的執行個體運作在那邊。
使用者一點上傳,可能上傳到了Tomcat2中,但是下次要顯示這個檔案時,可能用到的是Tomcat1内的jsp或者是class,對不對?
于是,因為你把圖檔存在了Tomcat的目錄中,是以導緻了Tomcat1在顯示圖檔時,取不到Tomcat2目錄中存放的圖檔。
是以我們在工程一開始就強調存圖檔時要用一台專門的檔案伺服器或者是FTP伺服器來存,就是為了避免将來出現這樣的問題。
我們的系統用到一個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,和我們想的是一緻的。
解決了上述的問題後基本我們可以開始布署Tomcat這個叢集了。
準備兩個版本一緻的Tomcat,分别起名為tomcat1,tomcat2。
² 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
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 /*fckeditor/editor/filemanager/connectors/*.* controller
JkMount /fckeditor/editor/filemanager/connectors/* controller
</VirtualHost>
注意:
原來的JKMount *** 後的 ajp13變成了什麼了?
controller
可以拿原有的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)找到
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"
改為:
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="tomcat2">
7)
在剛才的
的下面與在
<!-- 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的叢集節點進行輪循複制。
好了,現在啟動tomcat1, 啟動tomcat2(其實無所謂順序的),來看效果:
確定兩個tomcat節點都起來了,然後此時,我們啟動Apache
用sally/abcdefg登入,瞧,應用起來了。
然後我們拿另一台實體用戶端,登入這個web應用,我們可以看到:
第一個tomcat正在負責處理我們第一次登入的請求。
當有第二個HTTP請求時,另一個tomcat自動開始肩負起我們第二個HTTP請求了,這就是Load Balance。
<dl></dl>
<dt></dt>
<dd></dd>
學習了
真心感覺不錯,謝謝樓主分析,持續關注
你這裡有個錯誤,
worker.controller.sticky_session=0這樣一個tomcat挂掉以後另一個才能起作用。
我算是雞蛋裡挑骨頭了,真心寫的不錯了
樓主 加了你那一坨之後session不複制了 和解?
我把預設的注釋去掉可行....
引用“lifetragedy”的評論:回複zhimajiejie:很感興趣的是你能告訴我你把什麼存在SESSIO...
比如使用者選購的商品之類的東西,使用者的資訊等。我之前實習的一家公司遇到了這個問題。在多個tomcat中循環複制session,豈不是會降低效率,特别是tomcat很多的時候。
回複zhimajiejie:session裡除了使用者基本信什麼都不應該放.
如果你把每次查詢什麼商品資訊也放在了SESSION裡,導緻這個SESSION太大的話,比如說有人喜歡用SESSION做DB查詢結果暫存或者是用SESSION來做顯示分頁,由此造成的一切性能問題,自負.
另外一個問題是,當做tomcat叢集的時候,如果tomcat的數量太多的話,比如5-6個的時候,session的複制回導緻每個tomcat的性能下降的很厲害。請問樓主,有什麼好的辦法嗎?我從網上看說可以使用很多方式,一般是存儲到資料庫或者緩存中,但是對于現有程式的改動比較大。另外有人說可以使用Terracotta,問一下樓主有沒有這方面的經驗,Terracotta在生産環境中的使用怎樣?
回複zhimajiejie:很感興趣的是你能告訴我你把什麼存在SESSION裡了?
樓主,你好。按你的方法配置tomcat叢集的時候,發現session不能在多個tomat中進行複制,就是一個tomcat被關閉之後,其中的session不能複制到其他tomcat中。後來發現修改works.properties中的兩個配置參數就可以了
改為
worker.lbcontroller.sticky_session=1
worker.controller.sticky_session_force=false
不知道為什麼?
樓主,你好!有一個問題想向你請教。對于tomcat叢集中,application中存放的屬性,樓主是怎麼處理的呢?我們在項目中使用的是memcached存放原來需要存放到application中的屬性。
回複zhimajiejie:不知道你用的到底是哪個MEMCACHE,對于不同的MEMOCACHE處理是不同的.
有的CACHE支援叢集,即LOAD BALANCE
有的CACHE不支援叢集但支援HIGH AVAILABLE即CACHE是一份,始終是一份,然後這個節點死,另一個節點也布署着CACHE,那個CACHE會在另一個節點的CACHE死掉後的1-2秒内(可設定有的)自動起來,和QUARTZ的叢集原理一樣,一般這樣的CACHE有的甚至還會用資料庫來做persist
引用“zhimajiejie”的評論:樓主,你好!有一個問題想向你請教。對于tomcat叢集中,application中存放的屬性,樓主是...
感謝樓主的回複,我們使用的就是http://memcached.org/這個,這個不支援HA,是以還是不夠好。我按照樓主的提示再試試quartz
必須頂,正在進行第一遍的浏覽,以後會研究很多遍,希望部落客出更多好文章!
對了,發現tomcat的server.xml和你的有些差别,<Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
我們是 Catalina 而你提供的 裡面是 Standalone,但是想來這個應該沒大礙吧?
回複zxm94w:work.properties裡worklist指的是什麼?
它通過8009去解析jmv1, jmv2,這邊的jvm1必須與worklist指定的一緻。
自己多試試,這樣說吧,一樣新的東西試個100遍,成功一次,很正常,一旦成功了就是你自己的知識了,況且這東西不需要試100次。
樓主你好,一路按照流程走下來,一切都OK,唯獨最後這,叢集的時候,發現通過Apache伺服器通路的時候報500,但是單獨請求兩個tomcat的端口是正常的,再三檢查了配置檔案,沒有發現啥異常,求解!
部落客 ,我配置後 是500錯誤
直接通路兩個tomcat是沒問題的
回複zrarzw:帥哥,你的問題解決了麼?貌似我也遇到和你一樣的情況了。
回複zrarzw:你的WORK.PROPERTIES可能沒配好
回複lifetragedy:還有一個可能就是 workers.properties後面帶了空格
我一個tomcat可以 另外一個不行,
一點一點排除終于OK 感謝分享
回複zhujyy110:開源得東西就是這樣,有時搞多一個空格,打了個全角,少打個逗号一類得,沒有gui界面是要仔細點哈
回複lifetragedy:是的,TOMCAT是這樣的,商業的就不用這樣,有GUI配置界面。你可以看後面的WEBLOGIC叢集與WAS叢集
回複lifetragedy:worker.tomcat2.host這些要和tomcat裡面配置一樣
如果修改了tomcat的域名 這些地方都需要修改
部落客配了叢集後通路http://localhost/demoweb/ 報404錯誤 不知道哪裡有問題 我按照你的教材配的
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
加了這一坨之後報錯呀
Unable to start cluster.
org.apache.catalina.tribes.ChannelException: java.io.IOException: Invalid argument; No faulty members identified.
回複wangdan902:請仔細檢查自己的配置,相信是個小錯誤,不太好找,沒事,哪怕找個通宵找到問題了,這塊就學會了,是這樣的,沒辦法,都是這麼經曆過來的。
文中配置沒有問題,因為大都人都配起來了.
回複lifetragedy: <Membership className="org.apache.catalina.tribes.membership.McastService"
前幾天忙的沒時間來咯,應該說加了這段就報錯
回複wangdan902:你的TOMCAT版本?請用apache官網的tomcat6.0.20或以上版本,tomcat5和7不行,配置不一樣的。
回複lifetragedy:linux 64位
回複wangdan902:明了,linux下你的BROADCAST是不是被LINUX的防火牆給禁了。。。在LINUX下是有這個問題,需要把BROADCAST用route add加入,這點和WINDOWS下不一樣,嘿
回複lifetragedy:tomcat 6.0.35
超詳細易學!收藏了
學習啦
很贊 很多剛畢業的人需要這種指導
很不錯的文章,學習了。。。
噢!正是我一直想要了解東西,太好了,謝謝了,希望樓主能繼續發表這一系列文章!
>ü 原有TomcatServer上有8GB記憶體,而且是32位的,單台>Tomcat隻使用了1800MB左右的記憶體
32 JVM位的話,8G記憶體就是擺設啊,1800MB的使用基本上是最大值了。吞吐量上不去,CPU不高,但是記憶體高,我猜測GC比較頻繁。。 如果可以換64位試試吧。