為了便于理解集群的工作機(jī)制,下面將通過一些實(shí)際情境來加深一下你的理解,我們只打算采用 2 個(gè) Tomcat 實(shí)例:Tomcat A 和 Tomcat B。具體發(fā)生的事件流程為:
介紹完了事件序列,下面詳細(xì)剖析一下在會(huì)話復(fù)制代碼中到底發(fā)生了什么。
Tomcat A 啟動(dòng)
Tomcat 使用標(biāo)準(zhǔn)啟動(dòng)順序來啟動(dòng)。Host 對象創(chuàng)建好之后,會(huì)關(guān)聯(lián)一個(gè) Cluster 對象。在解析上下文時(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é)。
Tomcat B 啟動(dòng)
Tomcat B 啟動(dòng)時(shí),采取的順序與 Tomcat A 基本一樣。集群啟動(dòng),建立成員(Tomcat A 與 Tomcat B)。Tomcat B 會(huì)請求集群中已有服務(wù)器(本例中是 Tomcat A)的會(huì)話狀態(tài)。如果 Tomcat A 響應(yīng)該請求,那么在 Tomcat B 開始偵聽 HTTP 請求之前,Tomcat A 會(huì)將會(huì)話狀態(tài)傳到 Tomcat B那里;如果 Tomcat A 沒有響應(yīng)該請求,Tomcat 會(huì)等待 60 秒,超過這個(gè)時(shí)間之后,發(fā)出一個(gè)日志項(xiàng)。該會(huì)話狀態(tài)會(huì)發(fā)送到每一個(gè)在 web.xml 中設(shè)置了 distributable 元素的應(yīng)用。注意:為了有效地使用會(huì)話復(fù)制,所有的 Tomcat 實(shí)例都必須擁有相同的配置。
Tomcat A 接收一個(gè)請求,創(chuàng)建了一個(gè)會(huì)話 S1
Tomcat A 對發(fā)送給它的請求的處理方式,與沒有會(huì)話復(fù)制時(shí)的處理方式完全相同。請求完成時(shí)會(huì)觸發(fā)相應(yīng)行為,ReplicationValve 會(huì)在響應(yīng)返回用戶之前攔截請求。如發(fā)現(xiàn)會(huì)話已經(jīng)更改,則使用 TCP 將會(huì)話復(fù)制到 Tomcat B 上。一旦序列化的數(shù)據(jù)被轉(zhuǎn)交給操作系統(tǒng)的 TCP 邏輯,請求就會(huì)重新通過 valve 管道返回給用戶。對于每一個(gè)請求,都將復(fù)制所有的會(huì)話,這樣做就有利于復(fù)制那些在會(huì)話中修改屬性的代碼,使其即使不必調(diào)用 setAttribute 或 removeAttribute,也能被復(fù)制。另外,使用 useDirtyFlag 配置參數(shù)也可以優(yōu)化會(huì)話的復(fù)制次數(shù)。
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 的請求重新定向,所有的會(huì)話都將保持現(xiàn)有的狀態(tài)。
Tomcat B 接收到對會(huì)話 S1 的請求
毫無懸念,Tomcat B 會(huì)照處理其他請求的方式那樣來處理該請求。
Tomcat A 啟動(dòng)
在 Tomcat A 開始接收新的請求之前,將會(huì)根據(jù)上面(1)(2)兩條所所說明的啟動(dòng)序列來啟動(dòng)。Tomcat A 會(huì)加入集群,聯(lián)系 Tomcat B 并獲取所有的會(huì)話狀態(tài)。一旦接收到會(huì)話狀態(tài),就會(huì)完成加載,并打開 HTTP/mod_jk 端口。所以,除非 Tomcat A 從 Tomcat B 那里接收到了會(huì)話變更,否則沒有發(fā)給 Tomcat A 的請求。
Tomcat A 接收到一個(gè)請求,調(diào)用會(huì)話 S1 上的 invalidate 方法
會(huì)攔截對 invalidate 的調(diào)用, 并且 session 會(huì)被加入失效會(huì)話隊(duì)列。 在請求完成時(shí),不會(huì)發(fā)送會(huì)話改變消息,而是發(fā)送一個(gè) “到期” 消息給 Tomcat B,Tomcat B 也會(huì)讓此會(huì)話失效。
Tomcat B 接收到一個(gè)對新會(huì)話 S2 的請求
同步驟 3。
Tomcat A 會(huì)話 S2 由于不活躍而超時(shí)
invalidate 調(diào)用會(huì)被攔截,當(dāng)一個(gè)會(huì)話被用戶標(biāo)記失效時(shí),該會(huì)話就會(huì)加入到無效會(huì)話隊(duì)列。此時(shí),失效的會(huì)話不會(huì)被復(fù)制,直到另一個(gè)請求通過系統(tǒng)并檢查無效會(huì)話隊(duì)列。
Membership 集群成員是通過非常簡單的組播 ping 命令來實(shí)現(xiàn)的。每個(gè) Tomcat 實(shí)例都會(huì)定期發(fā)送一個(gè)組播 ping,ping 消息中包含 Tomcat 實(shí)例自身的 IP 和配置的 TCP 監(jiān)聽端口。如果實(shí)例在一個(gè)給定的時(shí)間內(nèi)沒有收到這樣的 ping 信息,就會(huì)認(rèn)為那個(gè)成員已經(jīng)崩潰了。非常簡潔高效!當(dāng)然,您需要在系統(tǒng)上啟用廣播。
TCP 復(fù)制 一旦收到一個(gè)多播 ping 包,在下一個(gè)復(fù)制請求時(shí)成員被添加到集群,發(fā)送實(shí)例將使用的主機(jī)和端口信息,以及建立TCP套接字。使用該套接字發(fā)送序列化的數(shù)據(jù)。之選擇TCP套接字,是因?yàn)樗鼉?nèi)建有流量控制和保證發(fā)送的功能。所以發(fā)送的數(shù)據(jù)肯定會(huì)到達(dá)那里。
分布式的鎖定與使用架構(gòu)的頁面s Tomcat 在跨集群同步不保持會(huì)話實(shí)例。這種邏輯的實(shí)現(xiàn)將是多開銷和導(dǎo)致各種各樣的問題。如果你的客戶用同一個(gè)會(huì)話同時(shí)發(fā)送多個(gè)請求,那么最后的請求將會(huì)覆蓋集群中的其他會(huì)話。