鍍金池/ 教程/ Linux/ 集群化與會(huì)話復(fù)制
連接器
JSPs
重寫(xiě)機(jī)制
CGI
Tomcat Manager
Windows 認(rèn)證
代理支持
虛擬主機(jī)
安全性注意事項(xiàng)
如何在 Maven 中使用 Tomcat 庫(kù)
安裝
MBean 描述符
JNDI 資源
類加載機(jī)制
Tomcat Web 應(yīng)用部署
基于 APR 的原生庫(kù)
負(fù)載均衡器
安全管理
附加組件
監(jiān)控與管理
Windows 服務(wù)
集群化與會(huì)話復(fù)制
高級(jí) IO 機(jī)制
SSI(服務(wù)器端嵌入)
WebSocket 支持
JDBC 數(shù)據(jù)源
日志機(jī)制
默認(rèn) Servlet
SSL/TLS 配置
Tomcat 的 JDBC 連接池
第一個(gè)應(yīng)用
簡(jiǎn)介
Realm 配置

集群化與會(huì)話復(fù)制

重要說(shuō)明

相關(guān)內(nèi)容詳情可以查看集群配置文檔

快速入門(mén)

只需將下列信息放入 <Engine><Host> 元素即可實(shí)現(xiàn)集群:

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>

上述配置啟用了全局(all-to-all)會(huì)話復(fù)制功能,全局會(huì)話復(fù)制是指利用 DeltaManager 來(lái)只復(fù)制會(huì)話中的變更Session Delta,也譯作“會(huì)話增量”)。這里說(shuō)的“全局”是指:會(huì)話變更會(huì)被復(fù)制到集群中的所有其他節(jié)點(diǎn)(指 Tomcat 實(shí)例)中。全局復(fù)制非常適于小集群,但不建議在大集群(包含很多 Tomcat 節(jié)點(diǎn))上采用這種方法。另外,值得注意的是,當(dāng)使用 delta manager 時(shí),它會(huì)將變更復(fù)制到所有的節(jié)點(diǎn)上,甚至包括那些根本沒(méi)有部署該應(yīng)用的節(jié)點(diǎn)。

為了解決這個(gè)問(wèn)題,你就得使用 BackupManager。它會(huì)把會(huì)話數(shù)據(jù)復(fù)制給一個(gè)指定的備份節(jié)點(diǎn)(這種復(fù)制也被稱為“配對(duì)復(fù)制”),而且該備份節(jié)點(diǎn)也一定要部署了相關(guān)應(yīng)用。BackupManager 的缺點(diǎn)在于:不像 DeltaManager 那樣久經(jīng)實(shí)踐考驗(yàn)。

下面是一些重要的默認(rèn)值。

  1. IP 組播地址為:228.0.0.4
  2. IP 組播端口為:45564(端口和地址一起確定了集群成員)。
  3. 廣播的 IP 是 java.net.InetAddress.getLocalHost().getHostAddress()(你一定不能廣播 127.0.0.1,這是一個(gè)常見(jiàn)錯(cuò)誤。)
  4. 偵聽(tīng)復(fù)制信息的 TCP 端口是在 4000 - 4100 之間遇到的第一個(gè)能用的服務(wù)器套接字。
  5. 兩個(gè)偵聽(tīng)器都配置有 ClusterSessionListener。
  6. 兩個(gè)攔截器都配置有 TcpFailureDetectorMessageDispatch15Interceptor。

下面是默認(rèn)的集群配置:

        <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.ClusterSessionListener"/>
        </Cluster>  

稍后,本文檔將更詳細(xì)地闡述這部分的內(nèi)容。

集群基本知識(shí)

要想在 Tomcat 8 上運(yùn)行會(huì)話復(fù)制,需要執(zhí)行以下步驟:

  • 所有的會(huì)話屬性必須實(shí)現(xiàn) java.io.Serializable
  • 在 server.xml 中取消注釋 Cluster 元素。
  • 如果你已經(jīng)定義了自定義集群值,確保在 server.xml 中的 Cluster 元素下面也定義了 ReplicationValve。
  • 如果你的多個(gè) Tomcat 實(shí)例都運(yùn)行在同一臺(tái)機(jī)器上,則要確保每個(gè)實(shí)例都具有唯一的 tcpListenPort。通常 Tomcat 會(huì)自行解決這個(gè)問(wèn)題,會(huì)在 4000 - 4100 上自動(dòng)偵測(cè)可用的端口。
  • 確保 web.xml 含有 <distributable/> 屬性。
  • 如果使用 mod_jk,則要確保在 <Engine name="Catalina" jvmRoute="node01" > 上設(shè)定 jvmRoute 屬性。jvmRoute 屬性值必須匹配 workers.properties 中的 worker 名稱。
  • 所有的節(jié)點(diǎn)必須具有相同的時(shí)間,并且與 NTP 服務(wù)器同步。
  • 確保負(fù)載均衡配置了會(huì)話模式.

負(fù)載均衡可以通過(guò)多種技術(shù)來(lái)實(shí)現(xiàn),參看負(fù)載均衡部分。

注意:會(huì)話狀態(tài)是通過(guò) cookie 來(lái)記錄的,所以你的 URL 必須保持一致,否則就會(huì)創(chuàng)建一個(gè)新會(huì)話。

注意:當(dāng)前如要支持集群,需要 JDK 1.5 或更新版本。

集群模塊使用 Tomcat 的 JULI 日志框架,所以可以通過(guò) logging.properties 文件來(lái)配置日志。為了跟蹤消息,你可以啟用 org.apache.catalina.tribes.MESSAGES 鍵上的日志。

概述

在 Tomcat 中,可以使用以下方法中的一種啟用會(huì)話復(fù)制:

  1. 使用會(huì)話持久性,將會(huì)話保存到共享文件系統(tǒng)中(PersistenceManager + FileStore)。
  2. 使用會(huì)話持久性,將會(huì)話保存到共享數(shù)據(jù)庫(kù)中(PersistenceManager + JDBCStore)。
  3. 使用內(nèi)存復(fù)制,使用 Tomcat 自帶的 SimpleTcpCluster(lib/catalina-tribes.jar + lib/catalina-ha.jar)。

在這一版本的 Tomcat 中,可以使用 DeltaManager 執(zhí)行全局式會(huì)話狀態(tài)復(fù)制,或者使用 BackupManager 執(zhí)行備份復(fù)制,將會(huì)話復(fù)制到一個(gè)節(jié)點(diǎn)上。全局式會(huì)話復(fù)制這種算法只有在集群較小時(shí)才比較有效。對(duì)于大型集群,更多使用主從會(huì)話復(fù)制,將會(huì)話存儲(chǔ)到一臺(tái)配置了 BackupManager 的備份服務(wù)器上。

當(dāng)前可以使用域名 worker 屬性(mod_jk 版本 > 1.2.8)來(lái)構(gòu)建集群分區(qū),從而有可能利用 DeltaManager 實(shí)現(xiàn)更具有可擴(kuò)展性的集群方案(需要為此配置域的攔截器)。為了在全局性環(huán)境中降低網(wǎng)絡(luò)流量,可以將集群分成幾個(gè)較小的分組。為不同的分組使用不同的組播地址即能實(shí)現(xiàn)這種方案。下圖展示的是一種簡(jiǎn)單的配置方案。

DNS 輪詢
|
負(fù) 載 均 衡 器
/              \
集群 1              集群 2
/     \             /     \
Tomcat 1 Tomcat 2  Tomcat 3 Tomcat 4  

值得注意的是,使用會(huì)話復(fù)制僅僅是集群化的一個(gè)基礎(chǔ)方案。關(guān)于集群的實(shí)現(xiàn),另一個(gè)常用的概念是耕種(farming),比如:只需將應(yīng)用部署到一個(gè)服務(wù)器上,集群就會(huì)將部署分發(fā)到整個(gè)集群的各個(gè)節(jié)點(diǎn)中。這都是 FarmWarDeployer 所具有的功能(參看 server.xml 中的集群范例)。

下一節(jié)將深入介紹會(huì)話復(fù)制的工作原理以及配置方式。

集群信息

通過(guò)組播心跳包(heartbeat)建立起成員(Membership)關(guān)系,因此,如果希望細(xì)分集群,可以改變 <Membership> 元素中的組播 IP 地址或端口。

心跳包中含有 Tomcat 節(jié)點(diǎn)的 IP 地址,以及 Tomcat 用來(lái)偵聽(tīng)會(huì)話復(fù)制流量的 TCP 端口。所有的數(shù)據(jù)通信都使用了 TCP 協(xié)議。

ReplicationValve 用于查找請(qǐng)求結(jié)束的時(shí)間,如果存在會(huì)話復(fù)制,就對(duì)該復(fù)制進(jìn)行初始化。只復(fù)制會(huì)話變更的數(shù)據(jù)(通過(guò)在會(huì)話上調(diào)用 setAttributeremoveAttribute 來(lái)完成)。

復(fù)制的異步與同步模式應(yīng)該是最值得我們注意的一個(gè)特點(diǎn)了。在同步復(fù)制模式下,復(fù)制的會(huì)話通過(guò)線纜傳送,重新在所有集群節(jié)點(diǎn)上實(shí)例化,這樣才會(huì)返回請(qǐng)求。同步和異步是通過(guò) channelSendOptions 標(biāo)志(整型值)來(lái)配置的。SimpleTcpCluster/DeltaManager 組合的默認(rèn)值是 8,從而是異步。詳情可以參考一下 send flag(overview)send flag(javadoc)。在異步復(fù)制過(guò)程中,請(qǐng)求不必等到數(shù)據(jù)被復(fù)制完畢即可返回。異步復(fù)制縮短了請(qǐng)求時(shí)間,而同步復(fù)制則保證了能在請(qǐng)求返回之前復(fù)制完會(huì)話。

當(dāng)發(fā)生崩潰時(shí),將會(huì)話綁定到故障轉(zhuǎn)移節(jié)點(diǎn)

如果你使用了 mod_jk 而沒(méi)有使用粘性會(huì)話(sticky session),或者粘性會(huì)話由于某種原因而不起作用,或者僅是故障轉(zhuǎn)移,會(huì)話 id 需要修改,因?yàn)樗昂兄?Tomcat 的 worker id(通過(guò) Engine 元素中的 jvmRoute 定義)。為了解決這個(gè)問(wèn)題,就要用到 JvmRouteBinderValve。

JvmRouteBinderValve 將重寫(xiě)會(huì)話 id,以便確保下一個(gè)請(qǐng)求在故障轉(zhuǎn)移后依然能保持粘性(不會(huì)因?yàn)?worker 不再可用而回滾到某個(gè)隨機(jī)的節(jié)點(diǎn)中)。利用同樣的名字,該值重寫(xiě)了 cookie 中的 JSESSIONID 值。假如沒(méi)有正確地設(shè)置 valve,將使 mod_jk 模塊在失敗后很難保持會(huì)話的粘性。

記住,如果在 server.xml 中自定義值,那么默認(rèn)值將不再有效,所以一定要確保添加了默認(rèn)所定義的值。

提示

利用屬性 sessionIdAttribute 可以改變包含舊會(huì)話 id 的請(qǐng)求屬性名。默認(rèn)的請(qǐng)求屬性名是:org.apache.catalina.ha.session.JvmRouteOrignalSessionID。

技巧

可以啟用 mod_jk 翻轉(zhuǎn)模式在刪除一個(gè)節(jié)點(diǎn), 然后啟用了 mod_jk Worker 禁用 JvmRouteBinderValves 。這種用例意味著只有請(qǐng)求的會(huì)話才能得到遷移。

配置范例

        <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
                 channelSendOptions="6">

          <Manager className="org.apache.catalina.ha.session.BackupManager"
                   expireSessionsOnShutdown="false"
                   notifyListenersOnReplication="true"
                   mapSendOptions="6"/>
          <!--
          <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="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|.*\.jpeg|.*\.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>

下面來(lái)仔細(xì)剖析一下這段代碼。

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="6">

Cluster 是主要元素,可在該元素內(nèi)配置所有的集群相關(guān)細(xì)節(jié)。 對(duì)于 SimpleTcpCluster 類或者調(diào)用 SimpleTcpCluster.send 方法的對(duì)象,它們所發(fā)出的每一個(gè)消息上都附加著一個(gè) channelSendOptions 標(biāo)志。關(guān)于發(fā)送標(biāo)志的描述可參見(jiàn)我們的 javadoc 文檔DeltaManager 使用 SimpleTcpCluster.send 方法發(fā)送信息,而備份管理器則直接通過(guò) channel 來(lái)發(fā)送自身。

更多詳細(xì)信息請(qǐng)參見(jiàn)集群配置參考文檔。

          <Manager className="org.apache.catalina.ha.session.BackupManager"
                   expireSessionsOnShutdown="false"
                   notifyListenersOnReplication="true"
                   mapSendOptions="6"/>
          <!--
          <Manager className="org.apache.catalina.ha.session.DeltaManager"
                   expireSessionsOnShutdown="false"
                   notifyListenersOnReplication="true"/>
          -->

如果在 <Context> 元素中沒(méi)有定義 manager,則以上可當(dāng)做 manager 的配置模板。在 Tomcat 5.x 時(shí)期,每個(gè)標(biāo)識(shí)為可分發(fā)(distributable)的 Web 應(yīng)用都必須使用同樣的 manager,而如今不同了,我們可以為每個(gè)應(yīng)用定義一個(gè) manager 類,從而在集群中混合多個(gè) manager。顯然,A 節(jié)點(diǎn)上的某個(gè)應(yīng)用的所有 manager 必須與 B 節(jié)點(diǎn)上的同樣應(yīng)用的 manager 相同。如果沒(méi)有為應(yīng)用指定 manager,而且該應(yīng)用被標(biāo)識(shí)為 <distributable/>,Tomcat 就會(huì)采取這種 manager 配置,創(chuàng)建一個(gè)克隆該配置的 manager 實(shí)例。

更多詳細(xì)信息請(qǐng)參見(jiàn)集群管理器文檔。

<Channel className="org.apache.catalina.tribes.group.GroupChannel">

Channel 元素是 Tribes 架構(gòu)的一個(gè)重要組成部分,Tribes 是 Tomcat 內(nèi)部所使用的分組通信架構(gòu)。Channel 元素封裝了所有通信相關(guān)事項(xiàng)以及成員邏輯。

詳情參見(jiàn)集群 Channel 文檔。

            <Membership className="org.apache.catalina.tribes.membership.McastService"
                        address="228.0.0.4"
                        port="45564"
                        frequency="500"
                        dropTime="3000"/>

成員關(guān)系(Membership)是通過(guò)組播來(lái)實(shí)現(xiàn)的。注意,如果你想將成員擴(kuò)展到組播范圍之外的某個(gè)點(diǎn)時(shí),Tribes 現(xiàn)在已經(jīng)能夠支持使用 StaticMembershipInterceptor 的靜態(tài)成員。address 屬性是所用的組播地址,port 是所用的組播端口號(hào)。這兩項(xiàng)組合起來(lái)將集群隔離開(kāi)。如果你希望一個(gè) QA 集群和一個(gè)生產(chǎn)集群,最簡(jiǎn)單的方法就是將 QA 集群的組播地址和端口號(hào)不同于生產(chǎn)集群的組播地址和端口號(hào)組合。

成員組件將其自身的 TCP 地址和端口廣播到其他節(jié)點(diǎn)處,從而使節(jié)點(diǎn)間的通信都可以通過(guò) TCP 協(xié)議來(lái)完成。請(qǐng)注意被廣播的 TCP 地址正是 Receiver.address 屬性值。

詳情參見(jiàn)集群成員文檔。

<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="auto"
port="5000"
selectorTimeout="100"
maxThreads="6"/>

在 Tribes 架構(gòu)中,數(shù)據(jù)的發(fā)送與接收以及被拆分為兩種功能性組件了。正如其名所示,Receiver 負(fù)責(zé)接收信息。由于 Tribes 與線程無(wú)關(guān)(其他架構(gòu)也開(kāi)始采用這一種常見(jiàn)改進(jìn)了),該組件內(nèi)部包含一個(gè)線程池,設(shè)定有 maxThreadsminThreads 兩種參數(shù)。

address 參數(shù)值是主機(jī)地址,由成員組件廣播到其他節(jié)點(diǎn)中。

關(guān)于更多詳情,可參看Receiver 文檔。

            <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
              <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
            </Sender>

Sender 組件負(fù)責(zé)將消息發(fā)送給其他節(jié)點(diǎn)。Sender 含有一個(gè) shell 組件 ReplicationTransmitter,但真正所要完成的任務(wù)則是通過(guò)子組件 Transport 來(lái)完成的。由于 Tribes 支持一個(gè) Sender 池,所以消息可以做到同步;如果使用的是 NIO Sender,你也可以并發(fā)地發(fā)送消息。

并發(fā)(Concurrently)意味著將同時(shí)有多個(gè)發(fā)送者對(duì)應(yīng)著一條消息,并行(Parallel)則意味著同時(shí)有多個(gè)消息對(duì)應(yīng)著多個(gè)發(fā)送者。詳情請(qǐng)參考這篇文檔。

            <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>

Tribes 利用了一個(gè)堆棧傳送消息。每個(gè)堆棧內(nèi)的元素都被稱為攔截器,跟 Tomcat 容器中的 valve 的作用差不多。使用攔截器,邏輯可被分成更容易管理的代碼段。上面配置中的攔截器:

  • TcpFailureDetector 通過(guò) TCP 核實(shí)崩潰的節(jié)點(diǎn)。如果組播包丟失,該攔截器就會(huì)防止誤報(bào)的情況出現(xiàn),比如,某個(gè)正在運(yùn)行的節(jié)點(diǎn)雖然活躍,但也被標(biāo)示為已崩潰。
  • MessageDispatch15Interceptor 分派消息到線程(線程池),異步發(fā)送消息。
  • ThroughputInterceptor 輸出對(duì)信息流量的簡(jiǎn)單統(tǒng)計(jì)。

請(qǐng)注意,攔截器的順序很重要。在 server.xml 中定義的順序正是它們出現(xiàn)在 channel 堆棧中的順序。這種機(jī)制就像是鏈表,最前面的是第一個(gè)攔截器,末尾的是最后一個(gè)攔截器。更多詳細(xì)資料,可參看這篇文檔

 <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
                 filter=".*\.gif|.*\.js|.*\.jpeg|.*\.jpg|.*\.png|.*\.htm|.*\.html|.*\.css|.*\.txt"/>

集群使用 valve 來(lái)跟蹤針對(duì) Web 應(yīng)用的請(qǐng)求。我們之前已經(jīng)提到過(guò) ReplicationValve 和 JvmRouteBinderValve。<Cluster> 元素本身并不是 Tomcat 管道的一部分,集群將 valve 添加到了它的父容器上,比如說(shuō) <Cluster> 元素被配置到 <Engine> 元素中,那么 valve 就會(huì)被加到 <Engine> 元素中。更多詳情,請(qǐng)參考集群 valve 配置文檔

          <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
                    tempDir="/tmp/war-temp/"
                    deployDir="/tmp/war-deploy/"
                    watchDir="/tmp/war-listen/"
                    watchEnabled="false"/>

默認(rèn)的 Tomcat 集群支持耕種部署(farmed deployment),比如說(shuō)集群可以在其他的節(jié)點(diǎn)上部署和取消部署應(yīng)用。該組件的狀態(tài)目前還不穩(wěn)定,但我們很快就會(huì)解決這個(gè)問(wèn)題。Tomcat 5.0 和 5.5 版本相比,在部署算法上有一點(diǎn)變化。組件的邏輯改變到部署目錄必須與應(yīng)用目錄相匹配。

更多詳情,請(qǐng)參考集群部署器文檔

          <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
        </Cluster>

因?yàn)?SimpleTcpCluster 本身既是 Channel 對(duì)象的發(fā)送者,又是接受者,所以組件可以將它們自身注冊(cè)成SimpleTcpCluster 的偵聽(tīng)器。 上面這個(gè)偵聽(tīng)器 ClusterSessionListener 將偵聽(tīng) DeltaManager 復(fù)制的消息,并將會(huì)話變更應(yīng)用到 manager 上,反過(guò)來(lái)應(yīng)用到會(huì)話上。

更多詳情,參看 集群偵聽(tīng)器文檔。

集群架構(gòu)

組件層級(jí):

Server
|
Service
|
Engine
|  \
|  --- Cluster --*
|
Host
|
------
/      \
Cluster    Context(1-N)
|             \
|             -- Manager
|                   \
|                   -- DeltaManager
|                   -- BackupManager
|
---------------------------
|                       \
Channel                    \
----------------------------- \
|                          \
Interceptor_1 ..               \
|                            \
Interceptor_N                    \
-----------------------------      \
|          |         |             \
Receiver    Sender   Membership       \
-- Valve
|      \
|       -- ReplicationValve
|       -- JvmRouteBinderValve
|
-- LifecycleListener
|
-- ClusterListener
|      \
|       -- ClusterSessionListener
|
-- Deployer
\
-- FarmWarDeployer

工作原理

為了便于理解集群的工作機(jī)制,下面將通過(guò)一些實(shí)際情境來(lái)加深一下你的理解,我們只打算采用 2 個(gè) Tomcat 實(shí)例:Tomcat ATomcat B。具體發(fā)生的事件流程為:

  1. Tomcat A 啟動(dòng)。
  2. Tomcat A 啟動(dòng)完畢后,Tomcat B 才啟動(dòng)。
  3. Tomcat A 接收一個(gè)請(qǐng)求,創(chuàng)建了一個(gè)會(huì)話 S1
  4. Tomcat A 崩潰。
  5. Tomcat B 接收到對(duì)會(huì)話 S1 的請(qǐng)求。
  6. Tomcat A 啟動(dòng)。
  7. Tomcat A 接收到一個(gè)請(qǐng)求,調(diào)用會(huì)話 S1 上的 invalidate 方法。
  8. Tomcat B 接收到一個(gè)對(duì)新會(huì)話 S2 的請(qǐng)求。
  9. Tomcat A 會(huì)話 S2 由于不活躍而超時(shí)。

介紹完了事件序列,下面詳細(xì)剖析一下在會(huì)話復(fù)制代碼中到底發(fā)生了什么。

1.Tomcat A 啟動(dòng)

Tomcat 使用標(biāo)準(zhǔn)啟動(dòng)順序來(lái)啟動(dòng)。Host 對(duì)象創(chuàng)建好之后,會(huì)關(guān)聯(lián)一個(gè) Cluster 對(duì)象。在解析上下文時(shí),如果 web.xml 中包含 distributable 元素,Tomcat 就會(huì)讓 Cluster 類(在該例中是 SimpleTcpCluster)創(chuàng)建復(fù)制的上下文的管理器。啟用了集群并在 web.xml 中設(shè)置了 distributable 元素后,Tomcat 會(huì)為該上下文創(chuàng)建一個(gè) DeltaManager(而不是 StandardManager)。Cluster 類會(huì)啟動(dòng)一個(gè)成員服務(wù)(組播)和一個(gè)復(fù)制服務(wù)(TCP 單播)。下文將會(huì)介紹更多的架構(gòu)細(xì)節(jié)。

2.Tomcat B 啟動(dòng)

Tomcat B 啟動(dòng)時(shí),采取的順序與 Tomcat A 基本一樣。集群?jiǎn)?dòng),建立成員(Tomcat A 與 Tomcat B)。Tomcat B 會(huì)請(qǐng)求集群中已有服務(wù)器(本例中是 Tomcat A)的會(huì)話狀態(tài)。如果 Tomcat A 響應(yīng)該請(qǐng)求,那么在 Tomcat B 開(kāi)始偵聽(tīng) HTTP 請(qǐng)求之前,Tomcat A 會(huì)將會(huì)話狀態(tài)傳到 Tomcat B那里;如果 Tomcat A 沒(méi)有響應(yīng)該請(qǐng)求,Tomcat 會(huì)等待 60 秒,超過(guò)這個(gè)時(shí)間之后,發(fā)出一個(gè)日志項(xiàng)。該會(huì)話狀態(tài)會(huì)發(fā)送到每一個(gè)在 web.xml 中設(shè)置了 distributable 元素的應(yīng)用。注意:為了有效地使用會(huì)話復(fù)制,所有的 Tomcat 實(shí)例都必須擁有相同的配置。

3.Tomcat A 接收一個(gè)請(qǐng)求,創(chuàng)建了一個(gè)會(huì)話 S1

Tomcat A 對(duì)發(fā)送給它的請(qǐng)求的處理方式,與沒(méi)有會(huì)話復(fù)制時(shí)的處理方式完全相同。請(qǐng)求完成時(shí)會(huì)觸發(fā)相應(yīng)行為,ReplicationValve 會(huì)在響應(yīng)返回用戶之前攔截請(qǐng)求。如發(fā)現(xiàn)會(huì)話已經(jīng)更改,則使用 TCP 將會(huì)話復(fù)制到 Tomcat B 上。一旦序列化的數(shù)據(jù)被轉(zhuǎn)交給操作系統(tǒng)的 TCP 邏輯,請(qǐng)求就會(huì)重新通過(guò) valve 管道返回給用戶。對(duì)于每一個(gè)請(qǐng)求,都將復(fù)制所有的會(huì)話,這樣做就有利于復(fù)制那些在會(huì)話中修改屬性的代碼,使其即使不必調(diào)用 setAttributeremoveAttribute,也能被復(fù)制。另外,使用 useDirtyFlag 配置參數(shù)也可以優(yōu)化會(huì)話的復(fù)制次數(shù)。

4.Tomcat A 崩潰

當(dāng) Tomcat A 崩潰時(shí),Tomcat B 會(huì)接到通知,得知 Tomcat A 已被移出集群,隨即 Tomcat B 就在其成員列表中也將 Tomcat A 移除,Tomcat B 從而不再收到關(guān)于 Tomcat A 的任何通知。負(fù)載均衡器會(huì)把從 Tomcat A 發(fā)送給 Tomcat B 的請(qǐng)求重新定向,所有的會(huì)話都將保持現(xiàn)有的狀態(tài)。

5.Tomcat B 接收到對(duì)會(huì)話 S1 的請(qǐng)求

毫無(wú)懸念,Tomcat B 會(huì)照處理其他請(qǐng)求的方式那樣來(lái)處理該請(qǐng)求。

6.Tomcat A 啟動(dòng)

在 Tomcat A 開(kāi)始接收新的請(qǐng)求之前,將會(huì)根據(jù)上面(1)(2)兩條所所說(shuō)明的啟動(dòng)序列來(lái)啟動(dòng)。Tomcat A 會(huì)加入集群,聯(lián)系 Tomcat B 并獲取所有的會(huì)話狀態(tài)。一旦接收到會(huì)話狀態(tài),就會(huì)完成加載,并打開(kāi) HTTP/mod_jk 端口。所以,除非 Tomcat A 從 Tomcat B 那里接收到了會(huì)話變更,否則沒(méi)有發(fā)給 Tomcat A 的請(qǐng)求。

7.Tomcat A 接收到一個(gè)請(qǐng)求,調(diào)用會(huì)話 S1 上的 invalidate 方法

會(huì)攔截對(duì) invalidate 的調(diào)用, 并且 session 會(huì)被加入失效會(huì)話隊(duì)列。 在請(qǐng)求完成時(shí),不會(huì)發(fā)送會(huì)話改變消息,而是發(fā)送一個(gè) “到期” 消息給 Tomcat B,Tomcat B 也會(huì)讓此會(huì)話失效。

8.Tomcat B 接收到一個(gè)對(duì)新會(huì)話 S2 的請(qǐng)求

同步驟 3。

9.Tomcat A 會(huì)話 S2 由于不活躍而超時(shí)

invalidate 調(diào)用會(huì)被攔截,當(dāng)一個(gè)會(huì)話被用戶標(biāo)記失效時(shí),該會(huì)話就會(huì)加入到無(wú)效會(huì)話隊(duì)列。此時(shí),失效的會(huì)話不會(huì)被復(fù)制,直到另一個(gè)請(qǐng)求通過(guò)系統(tǒng)并檢查無(wú)效會(huì)話隊(duì)列。

Membership 集群成員是通過(guò)非常簡(jiǎn)單的組播 ping 命令來(lái)實(shí)現(xiàn)的。每個(gè) Tomcat 實(shí)例都會(huì)定期發(fā)送一個(gè)組播 ping,ping 消息中包含 Tomcat 實(shí)例自身的 IP 和配置的 TCP 監(jiān)聽(tīng)端口。如果實(shí)例在一個(gè)給定的時(shí)間內(nèi)沒(méi)有收到這樣的 ping 信息,就會(huì)認(rèn)為那個(gè)成員已經(jīng)崩潰了。非常簡(jiǎn)潔高效!當(dāng)然,您需要在系統(tǒng)上啟用廣播。

TCP 復(fù)制 一旦收到一個(gè)多播 ping 包,在下一個(gè)復(fù)制請(qǐng)求時(shí)成員被添加到集群,發(fā)送實(shí)例將使用的主機(jī)和端口信息,以及建立TCP套接字。使用該套接字發(fā)送序列化的數(shù)據(jù)。之選擇TCP套接字,是因?yàn)樗鼉?nèi)建有流量控制和保證發(fā)送的功能。所以發(fā)送的數(shù)據(jù)肯定會(huì)到達(dá)那里。

分布式的鎖定與使用架構(gòu)的頁(yè)面s Tomcat 在跨集群同步不保持會(huì)話實(shí)例。這種邏輯的實(shí)現(xiàn)將是多開(kāi)銷和導(dǎo)致各種各樣的問(wèn)題。如果你的客戶用同一個(gè)會(huì)話同時(shí)發(fā)送多個(gè)請(qǐng)求,那么最后的請(qǐng)求將會(huì)覆蓋集群中的其他會(huì)話。

利用 JMX 監(jiān)控集群

使用集群時(shí),如何監(jiān)控是一個(gè)重要課題。有些集群對(duì)象是 JMX MBean。

添加下列屬性到啟動(dòng)腳本上。

set CATALINA_OPTS=\
-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=%my.jmx.port% \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.authenticate=false

下面是 Cluster 的 MBean 列表:

名稱 描述 MBean 對(duì)象名-引擎 MBean 對(duì)象名-主機(jī)
Cluster 完整的 cluster 元素 type=Cluster type=Cluster,host=${HOST}
DeltaManager 該管理器控制會(huì)話,并處理會(huì)話復(fù)制 type=Manager,context=${APP.CONTEXT.PATH}, host=${HOST} type=Manager,context=${APP.CONTEXT.PATH}, host=${HOST}
FarmWarDeployer 將一個(gè)應(yīng)用部署到該集群的所有節(jié)點(diǎn)上。 目前不支持 type=Cluster, host=${HOST}, component=deployer
Member 代表集群中的一個(gè)節(jié)點(diǎn) type=Cluster, component=member, name=${NODE_NAME} type=Cluster, host=${HOST}, component=memdber, name=${NODE_NAME}
ReplicationValve 該 valve 控制到備份節(jié)點(diǎn)的會(huì)話復(fù)制 type=Valve,name=ReplicationValve type=Valve,name=ReplicationValve,host=${HOST}
JvmRouteBinderValve 將 Session ID 變?yōu)?tomcat 當(dāng)前的 jvmroute 的集群回滾值 type=Valve,name=JvmRouteBinderValve, context=${APP.CONTEXT.PATH} type=Valve,name=JvmRouteBinderValve,host=${HOST}, context=${APP.CONTEXT.PATH}

常見(jiàn)問(wèn)題解答

請(qǐng)參看 FAQ:集群文檔。

上一篇:CGI下一篇:SSI(服務(wù)器端嵌入)