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

2022-03-03 13:56 更新

重要說(shuō)明

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

快速入門(mén)

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

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

上述配置啟用了全局(all-to-all)會(huì)話(huà)復(fù)制功能,全局會(huì)話(huà)復(fù)制是指利用 DeltaManager 來(lái)只復(fù)制會(huì)話(huà)中的變更Session Delta,也譯作“會(huì)話(huà)增量”)。這里說(shuō)的“全局”是指:會(huì)話(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ì)話(huà)數(shù)據(jù)復(fù)制給一個(gè)指定的備份節(jié)點(diǎn)(這種復(fù)制也被稱(chēng)為“配對(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ì)話(huà)復(fù)制,需要執(zhí)行以下步驟:

  • 所有的會(huì)話(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 名稱(chēng)。
  • 所有的節(jié)點(diǎn)必須具有相同的時(shí)間,并且與 NTP 服務(wù)器同步。
  • 確保負(fù)載均衡配置了會(huì)話(huà)模式.

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

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

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

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

概述

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

  1. 使用會(huì)話(huà)持久性,將會(huì)話(huà)保存到共享文件系統(tǒng)中(PersistenceManager + FileStore)。
  2. 使用會(huì)話(huà)持久性,將會(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ì)話(huà)狀態(tài)復(fù)制,或者使用 BackupManager 執(zhí)行備份復(fù)制,將會(huì)話(huà)復(fù)制到一個(gè)節(jié)點(diǎn)上。全局式會(huì)話(huà)復(fù)制這種算法只有在集群較小時(shí)才比較有效。對(duì)于大型集群,更多使用主從會(huì)話(huà)復(fù)制,將會(huì)話(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 輪詢(xún)
|
負(fù) 載 均 衡 器
/              \
集群 1              集群 2
/     \             /     \
Tomcat 1 Tomcat 2  Tomcat 3 Tomcat 4  

值得注意的是,使用會(huì)話(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ì)話(huà)復(fù)制的工作原理以及配置方式。

集群信息

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

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

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

復(fù)制的異步與同步模式應(yīng)該是最值得我們注意的一個(gè)特點(diǎn)了。在同步復(fù)制模式下,復(fù)制的會(huì)話(huà)通過(guò)線(xiàn)纜傳送,重新在所有集群節(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ì)話(huà)。

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

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

JvmRouteBinderValve 將重寫(xiě)會(huì)話(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ì)話(huà)的粘性。

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

提示

利用屬性 sessionIdAttribute 可以改變包含舊會(huì)話(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ì)話(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 類(lèi)或者調(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 類(lèi),從而在集群中混合多個(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 與線(xiàn)程無(wú)關(guān)(其他架構(gòu)也開(kāi)始采用這一種常見(jiàn)改進(jìn)了),該組件內(nèi)部包含一個(gè)線(xiàn)程池,設(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)的元素都被稱(chēng)為攔截器,跟 Tomcat 容器中的 valve 的作用差不多。使用攔截器,邏輯可被分成更容易管理的代碼段。上面配置中的攔截器:

  • TcpFailureDetector 通過(guò) TCP 核實(shí)崩潰的節(jié)點(diǎn)。如果組播包丟失,該攔截器就會(huì)防止誤報(bào)的情況出現(xiàn),比如,某個(gè)正在運(yùn)行的節(jié)點(diǎn)雖然活躍,但也被標(biāo)示為已崩潰。
  • MessageDispatch15Interceptor 分派消息到線(xiàn)程(線(xiàn)程池),異步發(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ì)話(huà)變更應(yīng)用到 manager 上,反過(guò)來(lái)應(yīng)用到會(huì)話(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ì)話(huà) S1。
  4. Tomcat A 崩潰。
  5. Tomcat B 接收到對(duì)會(huì)話(huà) S1 的請(qǐng)求。
  6. Tomcat A 啟動(dòng)。
  7. Tomcat A 接收到一個(gè)請(qǐng)求,調(diào)用會(huì)話(huà) S1 上的 invalidate 方法。
  8. Tomcat B 接收到一個(gè)對(duì)新會(huì)話(huà) S2 的請(qǐng)求。
  9. Tomcat A 會(huì)話(huà) S2 由于不活躍而超時(shí)。

介紹完了事件序列,下面詳細(xì)剖析一下在會(huì)話(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 類(lèi)(在該例中是 SimpleTcpCluster)創(chuàng)建復(fù)制的上下文的管理器。啟用了集群并在 web.xml 中設(shè)置了 distributable 元素后,Tomcat 會(huì)為該上下文創(chuàng)建一個(gè) DeltaManager(而不是 StandardManager)。Cluster 類(lèi)會(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ì)話(huà)狀態(tài)。如果 Tomcat A 響應(yīng)該請(qǐng)求,那么在 Tomcat B 開(kāi)始偵聽(tīng) HTTP 請(qǐng)求之前,Tomcat A 會(huì)將會(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ì)話(huà)狀態(tài)會(huì)發(fā)送到每一個(gè)在 web.xml 中設(shè)置了 distributable 元素的應(yīng)用。注意:為了有效地使用會(huì)話(huà)復(fù)制,所有的 Tomcat 實(shí)例都必須擁有相同的配置。

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

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

5.Tomcat B 接收到對(duì)會(huì)話(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ì)話(huà)狀態(tài)。一旦接收到會(huì)話(huà)狀態(tài),就會(huì)完成加載,并打開(kāi) HTTP/mod_jk 端口。所以,除非 Tomcat A 從 Tomcat B 那里接收到了會(huì)話(huà)變更,否則沒(méi)有發(fā)給 Tomcat A 的請(qǐng)求。

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

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

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

同步驟 3。

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

invalidate 調(diào)用會(huì)被攔截,當(dāng)一個(gè)會(huì)話(huà)被用戶(hù)標(biāo)記失效時(shí),該會(huì)話(huà)就會(huì)加入到無(wú)效會(huì)話(huà)隊(duì)列。此時(shí),失效的會(huì)話(huà)不會(huì)被復(fù)制,直到另一個(gè)請(qǐng)求通過(guò)系統(tǒng)并檢查無(wú)效會(huì)話(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ì)話(huà)實(shí)例。這種邏輯的實(shí)現(xiàn)將是多開(kāi)銷(xiāo)和導(dǎo)致各種各樣的問(wèn)題。如果你的客戶(hù)用同一個(gè)會(huì)話(huà)同時(shí)發(fā)送多個(gè)請(qǐng)求,那么最后的請(qǐng)求將會(huì)覆蓋集群中的其他會(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 列表:

名稱(chēng) 描述 MBean 對(duì)象名-引擎 MBean 對(duì)象名-主機(jī)
Cluster 完整的 cluster 元素 type=Cluster type=Cluster,host=${HOST}
DeltaManager 該管理器控制會(huì)話(huà),并處理會(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ì)話(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:集群文檔。

以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)