在筆者上一篇博客(http://zyycaesar.javaeye.com/admin/blogs/295227)中簡要介紹瞭如何經過簡單的配置來實現tomcat集羣,本文意在介紹對tomcat集羣進行更深刻詳細的配置以知足特定需求。css
對於WEB應用集羣的技術實現而言,最大的難點就是如何能在集羣中的多個節點之間保持數據的一致性,會話(Session)信息是這些數據中最重要的一塊。要實現這一點,大致上有兩種方式,一種是把全部Session數據放到一臺服務器上或者數據庫中,集羣中的全部節點經過訪問這臺Session服務器來獲取數據;另外一種就是在集羣中的全部節點間進行Session數據的同步拷貝,任何一個節點均保存了全部的Session數據。兩種方式都各有優勢,第一種方式簡單、易於實現,可是存在着Session服務器發生故障會致使全系統不能正常工做的風險;第二種方式可靠性更高,任一節點的故障不會對整個系統對客戶訪問的響應產生影響,可是技術實現上更復雜一些。常見的平臺或中間件如microsoft asp.net和IBM WAS都會提供對兩種共享方式的支持,tomcat也是這樣,可是通常採用第二種方式。html
當採用tomcat默認集羣配置(<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>)時,配置的細節實際上被省略了,對於大多數應用而言,使用默認配置已經足夠,完整的默認配置應該是這樣:前端
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="8">
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"/>
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService"
address="228.0.0.4"
port="45564"
frequency="500"
dropTime="3000"/>
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="auto"
port="4000"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
filter=""/>
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
<Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
tempDir="/tmp/war-temp/"
deployDir="/tmp/war-deploy/"
watchDir="/tmp/war-listen/"
watchEnabled="false"/>
<ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster> java
下面筆者對這裏的配置項做詳細解釋,如下內容均是筆者閱讀了tomcat官方文檔後本身的理解,有些可能不對,但願讀者能帶着批判的眼光閱讀,並歡迎指正筆者錯誤。web
tomcat集羣各節點經過創建tcp連接來完成Session的拷貝,拷貝有同步和異步兩種模式。在同步模式下,對客戶端的響應必須在Session拷貝到其餘節點完成後進行;異步模式無需等待Session拷貝完成就可響應。異步模式更高效,可是同步模式可靠性更高。同步異步模式由channelSendOptions參數控制,默認值是8,爲異步模式,4是同步模式。在異步模式下,能夠經過加上拷貝確認(Acknowledge)來提升可靠性,此時channelSendOptions設爲10。數據庫
Manager用來在節點間拷貝Session,默認使用DeltaManager,DeltaManager採用的一種all-to-all的工做方式,即集羣中的節點會把Session數據向全部其餘節點拷貝,而無論其餘節點是否部署了當前應用。當集羣中的節點數量不少而且部署着不一樣應用時,可使用BackupManager,BackManager僅向部署了當前應用的節點拷貝Session。可是到目前爲止BackupManager並未通過大規模測試,可靠性不及DeltaManager。apache
Channel負責對tomcat集羣的IO層進行配置。Membership用於發現集羣中的其餘節點,這裏的address用的是組播地址(Multicast address,瞭解更多組播地址詳情請參見http://zyycaesar.javaeye.com/admin/blogs/296501),使用同一個組播地址和端口的多個節點同屬一個子集羣,所以經過自定義組播地址和端口就可將一個大的tomcat集羣分紅多個子集羣。Receiver用於各個節點接收其餘節點發送的數據,在默認配置下tomcat會從4000-4100間依次選取一個可用的端口進行接收,自定義配置時,若是多個tomcat節點在一臺物理服務器上注意要使用不一樣的端口。Sender用於向其餘節點發送數據,具體實現經過Transport配置,PooledParallelSender是從tcp鏈接池中獲取鏈接,能夠實現並行發送,即集羣中的多個節點能夠同時向其餘全部節點發送數據而互不影響。Interceptor有點相似下面將要解釋的Valve,起到一個閥門的做用,在數據到達目的節點前進行檢測或其餘操做,如TcpFailureDetector用於檢測在數據的傳輸過程當中是否發生了tcp錯誤。關於Channel的編程模型,請參見http://tomcat.apache.org/tomcat-6.0-doc/api/org/apache/catalina/tribes/Channel.html。編程
Valve用於在節點向客戶端響應前進行檢測或進行某些操做,ReplicationValve就是用於用於檢測當前的響應是否涉及Session數據的更新,若是是則啓動Session拷貝操做,filter用於過濾請求,如客戶端對圖片,css,js的請求就不會涉及Session,所以不需檢測,默認狀態下不進行過濾,監測全部的響應。JvmRouteBinderValve會在前端的Apache mod_jk發生錯誤時保證同一客戶端的請求發送到集羣的同一個節點,tomcat官方文檔並未解釋如何實現這一點,並且筆者認爲這一設置彷佛並沒有多大實用性。api
Deployer用於集羣的farm功能,監控應用中文件的更新,以保證集羣中全部節點應用的一致性,如某個用戶上傳文件到集羣中某個節點的應用程序目錄下,Deployer會監測到這一操做並把這一文件拷貝到集羣中其餘節點相同應用的對應目錄下以保持全部應用的一致。這是一個至關強大的功能,不過很遺憾,tomcat集羣目前並不能作到這一點,開發人員正在努力實現它,這裏的配置只是預留了一個接口。tomcat
Listener用於跟蹤集羣中節點發出和收到的數據,也有點相似Valve的功能。
在大致瞭解了tomcat集羣實現模型後,就能夠對集羣做出更優化的配置了,tomcat推薦了一套配置,使用了比DeltaManager更高效的BackupManager,而且對ReplicationValve設置了請求過濾,注意在一臺服務器部署多個節點時須要修改Receiver的偵聽端口,另外,爲了更高效的在節點間拷貝數據,全部tomcat節點最好採用相同的配置,具體配置以下:
<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"
address="228.0.0.4"
port="45564"
frequency="500"
dropTime="3000"/>
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="auto"
port="5000"
selectorTimeout="100"
maxThreads="6"/>
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</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;"/>
<Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
tempDir="/tmp/war-temp/"
deployDir="/tmp/war-deploy/"
watchDir="/tmp/war-listen/"
watchEnabled="false"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
Tomcat集羣除了能夠進行Session數據的拷貝,還可進行Context屬性的拷貝,經過修改context.xml的Context配置能夠實現,使用<Context className="org.apache.catalina.ha.context.ReplicatedContext"/>替換默認Context便可,固然也可再加上distributable="true"屬性。
下面經過假想的一組場景來描述tomcat集羣如何工做,集羣採用默認配置,由t1和t2兩個tomcat例程組成,場景按照時間順序排列。
1. t1啓動
t1按照標準的tomcat啓動,當Host對象被建立時,一個Cluster對象(默認配置下是SimpleTcpCluster)也同時被關聯到這個Host對象。當某個應用在web.xml中設置了distributable時,Tomcat將爲此應用的上下文環境建立一個DeltaManager。SimpleTcpCluster啓動membership服務和Replication服務(用於創建tcp鏈接)。
2. t2啓動(待t1啓動完成後)
首先t2會執行和t1同樣的操做,而後SimpleTcpCluster會創建一個由t1和t2組成的membership。接着t2向集羣中已啓動的服務器即t1請求Session數據,若是t1沒有響應t2的拷貝請求,t2會在60秒後time out。在Session數據拷貝完成以前t2不會接收客戶端的http或mod_jk/ajp請求。
3. t1接收http請求,建立Session s1
t1正常響應客戶請求,可是在t1把結果發送回客戶端時,ReplicationValve會攔截當前請求(若是filter中配置了不需攔截的請求類型,這一步就不會進行,默認配置下攔截全部請求),若是發現當前請求更新了Session,調用Replication服務創建tcp鏈接把Session拷貝到membership列表中的其餘節點即t2,返回結果給客戶端(注意,若是採用同步拷貝,必須等拷貝完成後纔會返回結果,異步拷貝在數據發送到tcp鏈接就返回結果,不等待拷貝完成)。在拷貝時,全部保存在當前Session中的可序列化的對象都會被拷貝,而不只僅是發生更新的部分。
4. t1崩潰
當t1崩潰時,t2會被告知t1已從集羣中退出,而後t2就會把t1從本身的membership列表中刪除,發生在t2的Session更新再也不往t1拷貝,同時負載均衡器會把後續的http請求所有轉發給t2。在此過程當中全部的Session數據不會丟失。
5. t2接收s1的請求
t2正常響應s1的請求,由於t2保存着s1的全部數據。
6. t1從新啓動
按步驟一、2同樣的操做啓動,加入集羣,從t2拷貝全部Session數據,拷貝完成後開放本身的http和mod_jk/ajp端口接收請求。
7. t1接收請求,s1失效
t1繼續接收來自s1的請求,把s1設置爲過時。這裏的過時並不是由於s1處於非活動狀態超過設置的時間,而是執行相似註銷的操做而引發的Session失效。這時t1並不是發送s1的全部數據而是一個相似s1 expired的消息,t2收到消息後也會把s1設爲過時。
8. t2接收請求,建立Session s2
和步驟3同樣。
9. t1 s2過時 對於因超時引發的Session失效t1無需通知t2,由於t2一樣知道s2已經超時。所以對於tomcat集羣有一點很是重要,全部節點的操做系統時間必須一致!否則會出現某個節點Session已過時而在另外一節點此Session仍處於活動狀態的現象。