Tomcat集群Cluster實(shí)現(xiàn)原理剖析
Posted on 2009-06-27 16:28 J2EE Home工作室 閱讀(1435) 評論(0) 編輯 收藏 所屬分類: Apache + Tomcat
當(dāng)采用tomcat默認(rèn)集群配置(<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>)時,配置的細(xì)節(jié)實(shí)際上被省略了,對于大多數(shù)應(yīng)用而言,使用默認(rèn)配置已經(jīng)足夠,完整的默認(rèn)配置應(yīng)該是這樣:
<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>
下面筆者對這里的配置項作詳細(xì)解釋,以下內(nèi)容均是筆者閱讀了tomcat官方文檔后自己的理解,有些可能不對,希望讀者能帶著批判的眼光閱讀,并歡迎指正筆者錯誤。
tomcat集群各節(jié)點(diǎn)通過建立tcp鏈接來完成Session的拷貝,拷貝有同步和異步兩種模式。在同步模式下,對客戶端的響應(yīng)必須在Session拷貝到其他節(jié)點(diǎn)完成后進(jìn)行;異步模式無需等待Session拷貝完成就可響應(yīng)。異步模式更高效,但是同步模式可靠性更高。同步異步模式由channelSendOptions參數(shù)控制,默認(rèn)值是8,為異步模式,4是同步模式。在異步模式下,可以通過加上拷貝確認(rèn)(Acknowledge)來提高可靠性,此時channelSendOptions設(shè)為10。
Manager用來在節(jié)點(diǎn)間拷貝Session,默認(rèn)使用DeltaManager,DeltaManager采用的一種all-to-all的工作方式,即集群中的節(jié)點(diǎn)會把Session數(shù)據(jù)向所有其他節(jié)點(diǎn)拷貝,而不管其他節(jié)點(diǎn)是否部署了當(dāng)前應(yīng)用。當(dāng)集群中的節(jié)點(diǎn)數(shù)量很多并且部署著不同應(yīng)用時,可以使用BackupManager,BackManager僅向部署了當(dāng)前應(yīng)用的節(jié)點(diǎn)拷貝Session。但是到目前為止BackupManager并未經(jīng)過大規(guī)模測試,可靠性不及DeltaManager。
Channel負(fù)責(zé)對tomcat集群的IO層進(jìn)行配置。Membership用于發(fā)現(xiàn)集群中的其他節(jié)點(diǎn),這里的address用的是組播地址(Multicast address,了解更多組播地址詳情請參見http://zyycaesar.javaeye.com/admin/blogs/296501),使用同一個組播地址和端口的多個節(jié)點(diǎn)同屬一個子集群,因此通過自定義組播地址和端口就可將一個大的tomcat集群分成多個子集群。Receiver用于各個節(jié)點(diǎn)接收其他節(jié)點(diǎn)發(fā)送的數(shù)據(jù),在默認(rèn)配置下tomcat會從4000-4100間依次選取一個可用的端口進(jìn)行接收,自定義配置時,如果多個tomcat節(jié)點(diǎn)在一臺物理服務(wù)器上注意要使用不同的端口。Sender用于向其他節(jié)點(diǎn)發(fā)送數(shù)據(jù),具體實(shí)現(xiàn)通過Transport配置,PooledParallelSender是從tcp連接池中獲取連接,可以實(shí)現(xiàn)并行發(fā)送,即集群中的多個節(jié)點(diǎn)可以同時向其他所有節(jié)點(diǎn)發(fā)送數(shù)據(jù)而互不影響。Interceptor有點(diǎn)類似下面將要解釋的Valve,起到一個閥門的作用,在數(shù)據(jù)到達(dá)目的節(jié)點(diǎn)前進(jìn)行檢測或其他操作,如TcpFailureDetector用于檢測在數(shù)據(jù)的傳輸過程中是否發(fā)生了tcp錯誤。關(guān)于Channel的編程模型,請參見http://tomcat.apache.org/tomcat-6.0-doc/api/org/apache/catalina/tribes/Channel.html。
Valve用于在節(jié)點(diǎn)向客戶端響應(yīng)前進(jìn)行檢測或進(jìn)行某些操作,ReplicationValve就是用于用于檢測當(dāng)前的響應(yīng)是否涉及Session數(shù)據(jù)的更新,如果是則啟動Session拷貝操作,filter用于過濾請求,如客戶端對圖片,css,js的請求就不會涉及Session,因此不需檢測,默認(rèn)狀態(tài)下不進(jìn)行過濾,監(jiān)測所有的響應(yīng)。JvmRouteBinderValve會在前端的Apache mod_jk發(fā)生錯誤時保證同一客戶端的請求發(fā)送到集群的同一個節(jié)點(diǎn),tomcat官方文檔并未解釋如何實(shí)現(xiàn)這一點(diǎn),而且筆者認(rèn)為這一設(shè)置似乎并無多大實(shí)用性。
Deployer用于集群的farm功能,監(jiān)控應(yīng)用中文件的更新,以保證集群中所有節(jié)點(diǎn)應(yīng)用的一致性,如某個用戶上傳文件到集群中某個節(jié)點(diǎn)的應(yīng)用程序目錄下,Deployer會監(jiān)測到這一操作并把這一文件拷貝到集群中其他節(jié)點(diǎn)相同應(yīng)用的對應(yīng)目錄下以保持所有應(yīng)用的一致。這是一個相當(dāng)強(qiáng)大的功能,不過很遺憾,tomcat集群目前并不能做到這一點(diǎn),開發(fā)人員正在努力實(shí)現(xiàn)它,這里的配置只是預(yù)留了一個接口。
Listener用于跟蹤集群中節(jié)點(diǎn)發(fā)出和收到的數(shù)據(jù),也有點(diǎn)類似Valve的功能。
在大體了解了tomcat集群實(shí)現(xiàn)模型后,就可以對集群作出更優(yōu)化的配置了,tomcat推薦了一套配置,使用了比DeltaManager更高效的BackupManager,并且對ReplicationValve設(shè)置了請求過濾,注意在一臺服務(wù)器部署多個節(jié)點(diǎn)時需要修改Receiver的偵聽端口,另外,為了更高效的在節(jié)點(diǎn)間拷貝數(shù)據(jù),所有tomcat節(jié)點(diǎn)最好采用相同的配置,具體配置如下:
<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集群除了可以進(jìn)行Session數(shù)據(jù)的拷貝,還可進(jìn)行Context屬性的拷貝,通過修改context.xml的Context配置可以實(shí)現(xiàn),使用<Context className="org.apache.catalina.ha.context.ReplicatedContext"/>替換默認(rèn)Context即可,當(dāng)然也可再加上distributable="true"屬性。
下面通過假想的一組場景來描述tomcat集群如何工作,集群采用默認(rèn)配置,由t1和t2兩個tomcat例程組成,場景按照時間順序排列。
1. t1啟動
t1按照標(biāo)準(zhǔn)的tomcat啟動,當(dāng)Host對象被創(chuàng)建時,一個Cluster對象(默認(rèn)配置下是SimpleTcpCluster)也同時被關(guān)聯(lián)到這個Host對象。當(dāng)某個應(yīng)用在web.xml中設(shè)置了distributable時,Tomcat將為此應(yīng)用的上下文環(huán)境創(chuàng)建一個DeltaManager。SimpleTcpCluster啟動membership服務(wù)和Replication服務(wù)(用于建立tcp連接)。
2. t2啟動(待t1啟動完成后)
首先t2會執(zhí)行和t1一樣的操作,然后SimpleTcpCluster會建立一個由t1和t2組成的membership。接著t2向集群中已啟動的服務(wù)器即t1請求Session數(shù)據(jù),如果t1沒有響應(yīng)t2的拷貝請求,t2會在60秒后time out。在Session數(shù)據(jù)拷貝完成之前t2不會接收客戶端的http或mod_jk/ajp請求。
3. t1接收http請求,創(chuàng)建Session s1
t1正常響應(yīng)客戶請求,但是在t1把結(jié)果發(fā)送回客戶端時,ReplicationValve會攔截當(dāng)前請求(如果filter中配置了不需攔截的請求類型,這一步就不會進(jìn)行,默認(rèn)配置下攔截所有請求),如果發(fā)現(xiàn)當(dāng)前請求更新了Session,調(diào)用Replication服務(wù)建立tcp連接把Session拷貝到membership列表中的其他節(jié)點(diǎn)即t2,返回結(jié)果給客戶端(注意,如果采用同步拷貝,必須等拷貝完成后才會返回結(jié)果,異步拷貝在數(shù)據(jù)發(fā)送到tcp連接就返回結(jié)果,不等待拷貝完成)。在拷貝時,所有保存在當(dāng)前Session中的可序列化的對象都會被拷貝,而不僅僅是發(fā)生更新的部分。
4. t1崩潰
當(dāng)t1崩潰時,t2會被告知t1已從集群中退出,然后t2就會把t1從自己的membership列表中刪除,發(fā)生在t2的Session更新不再往t1拷貝,同時負(fù)載均衡器會把后續(xù)的http請求全部轉(zhuǎn)發(fā)給t2。在此過程中所有的Session數(shù)據(jù)不會丟失。
5. t2接收s1的請求
t2正常響應(yīng)s1的請求,因?yàn)閠2保存著s1的所有數(shù)據(jù)。
6. t1重新啟動
按步驟1、2一樣的操作啟動,加入集群,從t2拷貝所有Session數(shù)據(jù),拷貝完成后開放自己的http和mod_jk/ajp端口接收請求。
7. t1接收請求,s1失效
t1繼續(xù)接收來自s1的請求,把s1設(shè)置為過期。這里的過期并非因?yàn)閟1處于非活動狀態(tài)超過設(shè)置的時間,而是執(zhí)行類似注銷的操作而引起的Session失效。這時t1并非發(fā)送s1的所有數(shù)據(jù)而是一個類似s1 expired的消息,t2收到消息后也會把s1設(shè)為過期。
8. t2接收請求,創(chuàng)建Session s2
和步驟3一樣。
9. t1 s2過期
對于因超時引起的Session失效t1無需通知t2,因?yàn)閠2同樣知道s2已經(jīng)超時。因此對于tomcat集群有一點(diǎn)非常重要,所有節(jié)點(diǎn)的操作系統(tǒng)時間必須一致!不然會出現(xiàn)某個節(jié)點(diǎn)Session已過期而在另一節(jié)點(diǎn)此Session仍處于活動狀態(tài)的現(xiàn)象。