程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 高影響力的Web層群集,第一部分: 利用JavaGroups擴展

高影響力的Web層群集,第一部分: 利用JavaGroups擴展

編輯:關於JAVA

隨著J2EE平台的日益成熟,為了在Web層上擴展Web服務及應用,可以在聯網的群集配置中 部署廉價服務器(commodityservers )。這些廉價服務器通過廉價的LAN硬件相互連接在一 起,可以提供成本合適的群集解決方案。最後一個群集難題在於軟件方面。在本系列文章中 ,SingLi分析了三種可以允許高影響力Web層群集的開放源代碼軟件基礎,首先介紹 JavaGroups。

在 Internet 上,基於J2EE的Web應用及Web服務的流行,將同時處理上千個(或者更多) 用戶的需求推向前台。在許多商業部署中,這不再是一個“未來可用的”奢侈品 ,而是成為必需品。在這種競爭的商業環境中,一個在線商店如果顧客一多就掛起或崩潰, 它是不會生存下去的。盡管針對 J2EE 模型的事務層(用於數據庫、事務監聽器、消息隊列 ,等等)的可擴展解決方案已經廣泛可用,但是用於在Web層擴展 Web 應用或服務的解決方 案還剛剛浮現。在該系列文章中,我們將要看幾個可應用於在 Web 層擴展應用的軟件技術。 每種技術都采用一種不同的方法,並且解決一個稍微不同的問題集合。在這第一篇文章中, 我們將探討一種流行的開放源代碼的分布式通信基礎,那就是 JavaGroups。

在Web層擴展應用

擴展Web層有多種經過檢驗而可靠的方法。增加一個服務處理的並發會話的數量最直觀的 方法是向應用服務器增加資源。這些資源包括內存與磁盤空間(存儲資源)和 CPU (計算資源) 。圖 1 演示了可擴展性的這一單機器方法:

圖 1. 在單個 SMP 服務器上擴展 Web 層

該方法的障礙在於處理器使用的地址空間上的限制和廉價對稱多處理器(symmetric multiprocessor,SMP)硬件上的限制(合理的成本)。服務器配置超過四個處理器,就可能需 要專用的或定制的硬件來處理資源負載,並且對系統的掌握和維護很快就變得昂貴起來。這 些限制給我們利用單服務器解決方案在 Web 層上所能處理的用戶會話數量施加了一個實際的 上限。

除了在會話方面的限制以外,由於它的單點故障,單服務器解決方案通常也不是一種健壯 的解決方案。可用性是不連續的,因為當單個服務器出現故障時,服務就是不可用的。盡管 該問題有多種可行的技術上的解決方案(比如可熱切換的、冗余的備份資源),但是這些解決 方案都非常昂貴。

微型和小型計算機系統廠商早就已將 群集作為可伸縮性問題的一個可行的解決方案。群 集允許一組服務器 (通常是松散耦合的)邏輯上作為單個服務器運行。群集的優點包括:

消除了單點故障。

高服務可用性(如果群集中的多個服務器可以處理同一服務)。

通過將請求轉移給處理同一服務的負載最少的服務器所帶來的負載均衡。

近來,由於許多利好因素,群集已經“納入主流”:

J2EE Web層容器 (應用服務器) 技術終趨成熟,並且它們的狀態管理和操作模型也得到了 較好的規范和理解。通過在一群服務器之間復制 Web 層容器的狀態,您可以實現可伸縮的服 務解決方案。

廉價的基於 PC 的服務器的成本達到了歷史較低水平 (而每台服務器的 CPU 處理能力則 持續增長),使得群集的成本比以前更加可以承受。

高速的基於 LAN 的互連廣泛可用並且便宜。同時,套接字、TCP/IP 和更高級別的網絡 API 使得編程需求非常簡單。現在您使用用於群集的基於 LAN 的互連來取代專用的硬件連接 /總線互連。

開放源代碼 Linux 操作系統的廣泛采用,甚至使得定制的群集解決方案可以以非專用的 方式實現、維護和維持下去。

盡管這些 LAN 連接的、基於廉價服務器的群集解決方案,通常不具有那些良好校准的、 精確調整的、定制設計的專用系統那麼強硬的保證,但是它們確實為實現可伸縮的、可用的 、負載平衡的系統提供了一種高性價比的解決方案。圖 2 演示了該基於廉價硬件的解決方案 的拓撲結構:

圖 2. 利用聯網的服務器群集擴展 Web 層

當然,有了適當的硬件只是解決方案的一半。不要為每個特定的應用編寫定制的網絡通信 軟件,理想情況是為創建群集解決方案找到一些通用的“膠水”軟件。這是實際 研究的一個新興領域,並且是使廉價群集解決方案成為現實的決定性因素。在討論 JavaGroups (開放源代碼的分布式通信基礎) 如何提供這種膠水之前,我們首先很好地來了 解一下 Web 層擴展問題。

了解 Web 層擴展問題

設想一個購物者在一個在線商店裡。她已經浏覽了商品目錄,並將幾件商品放到了她的購 物車中。典型的購物車實現在服務器上管理一個會話。這個會話的關鍵字要麼作為一個 cookie存儲在客戶端浏覽器上,要麼存儲為附加了會話 ID 信息的新的 URL。來自她的浏覽 器的後續請求將會發送回這一會話 ID,使得服務器可以跟蹤她的會話。許多購物者可能會同 時在線,而在線商店服務必須管理所有的會話。在我們的場景中,我們假設這些會話是非持 久的,並且假設它們由在線商店服務存儲在內存中。

擴展問題是,如果在線商店站點實際上由一群機器來服務,對特定會話的連續請求必須都 被定向到同一台機器 (因為該會話只存儲在這一台機器上)。通過具體化會話以及在一群服務 器之間復制會話,群集中的所有服務器都可以為任何復制的會話取得傳進來的購物者請求。

完全有可能編寫我們自己定制的網絡軟件來處理這樣的會話復制。然而由於網絡硬件故障 的可能性,這樣的軟件很難編寫、測試和維護。有幸的是,JavaGroups 為群集中的會話復制 提供了馬上可以部署的解決方案。

為了理解這一復制是如何工作的,以及為什麼有些開放源代碼的應用服務器選擇了 JavaGroups 來進行會話復制,讓我們更詳細地來了解一下 JavaGroups。

JavaGroups 體系結構

JavaGroups 是一個軟件工具包 (API 庫),用於設計、實現和實驗分布式的系統解決方案 (更確切地說,在理論領域中叫做 進程組通信)。JavaGroups 的體系結構分為兩個相關的部 分,如圖 3 所示。一個叫做 通道的 Java API 抽象是兩個部分相分離的邊界。

圖 3. JavaGroups 概念上的體系結構

這一邊界也分離了潛在的 JavaGroups 用戶的兩個截然不同的角色: 分布式的應用開發 者和 協議實現者。

JavaGroups 用戶

在通道邊界的上部是分布式的應用開發者,他們將使用 JavaGroups 作為基礎來執行分布 式的操作。事實上,這就是我們的角色――我們是分布式的應用開發者,將使用 JavaGroups 來實現 Web 層群集。

在通道邊界的下部,JavaGroups支持一個靈活的、可堆疊的、可運行時配置的、100%純 Java 協議的棧框架。這對於通信協議的實驗者、設計者和實現者來說是想法變成現實的地方 。使用該框架,您能夠以一兩頁 Java 代碼編寫和測試一個適度復雜的協議實現,並且易於 調試、維護和改進。在協議框架級別編程超出了本系列文章的范疇,但是感興趣的讀者可以 參考 參考資料部分的"JavaGroups 用戶指南" 。

虛擬同步與隨機廣播

JavaGroups 微協議的基本集合中包括 JChannel 實現,為協議棧的服務質量提供了非常 強大的保證。組管理服務(group management service,GMS)基於虛擬同步模型 (參見 參考 資料,以找到一本關於該主題的參考書)。每個成員根據時間順序安裝一個序列的視圖 (成員 關系列表) ,因而保證能接收到視圖之間的同一組消息。一個視圖中發送的任何消息也能保 證在該視圖中接收到。盡管對於小的成員關系是穩定的,但是這種實現對於非常大的成員關 系卻缺乏可伸縮性。事實上,JavaGroups 中的虛擬同步實現對於大的組成員關系是相當有問 題的。

為了支持非常大的成員關系――這種情況下,隨機廣播比絕對保證更加可接受―― JavaGroups 提供了一組基於 隨機廣播的協議。隨著成員關系的增長,這些協議是可擴展的 和穩定的。

JavaGroups 通道

通道是一個類似於套接字的實體,具有大量添加進來的值,以使我們的分布式編程更加容 易。作為分布式的應用開發者,我們在通道上面編程,而通道為我們訪問 JavaGroups 提供 的豐富的協議支持提供保障。

就跟套接字一樣,通道有一個與之相關的地址,並且是我們用來發送和接收數據的對象。 但是與套接字不同的是,與通道相關的地址是不透明的 (應用開發者不必知道該地址的物理 細節),並且我們從通道發送和接收的數據是消息――是比套接字的分組更高級別的實體。

為了對進程組通信有用,一個通道將與一組進程相關。每個通道都有一個與之相關的文本 化的通道名稱 (有時叫做 組地址) ,並且具有相同名稱的通道實例在邏輯上屬於相同的組。

分布式網絡中的組成員關系管理解決(或者編程)起來不是一個容易的問題。事實上,組 成員關系管理是 JavaGroup 最有價值的特性之一。通道抽象下面的協議棧可以為我們執行組 成員關系管理,在成員加入和離開組時對它們進行跟蹤。您甚至可以選擇使用基於虛擬同步 的算法或者是基於隨機廣播的算法 (參見 虛擬同步與隨機廣播)。

地址和通道名稱一起惟一地標識一個通道實例。要使用通道實例,必須首先與之連接。一 次只可以有一個進程連接到一個通道實例。您也可以斷開與一個通道實例的連接,以將它釋 放給其他進程使用,也可以關閉一個通道實例以永久地禁用它。圖 4 演示了通道實例如何方 便進程組通信:

圖 4. 利用 JavaGroups 通道進行通信

圖 4 顯示了三台機器上四個通過 LAN 的通道實例。注意,所有的通道實例具有相同的通 道名稱,但是具有不同的物理地址。在這種情況下,每個物理地址包含一個 <IP 地址: 端口>, ,因而允許同一主機(一個 IP 地址)上的多個物理地址。另外注意,只有一個進 程連接到每個通道實例。四個進程全都屬於同一個組。

進程組中的通信

您可以通過通道發送一個消息到組的一個特定成員 ( 單播),或者發送到組的所有成員 ( 多播)。要發送消息到組的特定成員,您需要它的地址。

既然通道為我們管理組成員關系,因此您總可以通過從通道檢索成員關系列表來獲得當前 “視圖”中的成員。另外,您也可以在視圖更改(成員關系更改通知) 發生時從通 道了解到這一更改。圖 5 演示了分布式應用開發者和通道抽象之間職責的分離:

圖 5. JavaGroups 通道的功能視圖

可重用的通信編碼模式

為了進一步簡化分布式的應用編程,JavaGroups 以 Java 類的形式提供了一組經常使用 的通信編碼模式。您可以使用多個這些 編程模式(也叫做 構造塊) ,來代替或補充對通道抽 象的直接訪問。

在 org.javagroups.blocks 包中可以找到所有這些模式。表 1 顯示了最有用的構造塊的 部分列表:

表 1. 編碼模式構造塊

類名稱 描述 PullPushAdapter 減少用戶在通道中檢查入站消息的需要。用戶注冊一個偵聽器,而適配器 將回調入站消息的收據或者視圖的更改 MessageDispatcher 封裝一個到所有成員的請求的同步發送,並關聯響應的收據。可以等待第 一個響應、所有響應、特定數量的響應、大多數響應,或者一直等待到超時。通過注冊一個 MessageListener ,可以使用的API將更加豐富。另外,用戶可以注冊一個 RequestHandler , 以處理到通道的入站請求 RpcDispatcher 位於 MessageDispatcher 的上面一層,添加遠程方法調用語義到消息分配 器管理的場景。允許用戶調用遠程方法,並且關聯來自所有或一部分成員的返回值。通過對 用戶提供的服務器對象使用反射,還支持來自其他 RpcDispatcher 實例的入站遠程調用。 ConnectionTable 一個 TCP 連接管理器,它使用線程池來處理入站連接。重用現有的出站 TCP 連接來發送消息

編碼模式可適用於許多分布式設計,並且特別地被創建用來與 JavaGroups 通道很好地協 同工作。例如,通過對 RpcDispatcher 編程,您可以大大地減少涉及遠程過程調用語義的分 布式應用所需的代碼。

馬上可以使用的分布式數據結構

org.javagroups.blocks 包中的其他編碼模式提供完整的、馬上可以使用的、高級別的分 布式數據結構,其中的一部分列表顯示在表 2 中:

表 2. 高級別的分布式數據結構作為構造塊

類名稱 描述 ReplicatedTree 管理一個完整的分布式樹數據結構,可靠地將所有更改復制到組成員。任 何成員都可以添加和刪除節點 DistributedHashtable 實現一個復制的hash表,該復制的hash表將hash表的更改傳播到所有組成 員 NotificationBus 自包含的構造塊 (即創建自己的通道) ,它實現一個通知總線,在該通知 總線中,消費者可以為生產者發送的通知進行注冊。每個組成員可以參與任何一個或兩個角 色。被設計為支持一個復制緩存的實現

插入式通道實現

到目前為止,我們已經談論了 JavaGroups 通道,就好像它是一個具體實現一樣。但是, 通道實際上是 JavaGroups 中的一個抽象的類。實際上,當前的 JavaGroups 分布帶有多個 通道實現,如圖 6 所示:

圖 6. 通道抽象和具體的實現

JChannel

EnsembleChannel 通過一個用 Java 編寫的連接器訪問 Ensemble (參見 參考資料), Ensemble 是一個健壯的進程組通信基礎 (非Java)。

通過創建您自己的通道實現也可以擴展 JavaGroups。

利用 JavaGroups 進行編程

JavaGroups 的一個典型使用場景涉及以下步驟:

實例化一個通道並初始化必需的協議棧。

連接到通道。

開始發送消息或者處理入站消息 (有可能是在構造塊的幫助下)。

斷開連接並關閉通道。

假設我們使用的是 JChannel, 我們只要設置一個初始字符串就可以創建一個協議棧 (或 者也可能用到一個外部的基於 XML 的配置文件――參見 參考資料 部分中的 “JavaGroups 用戶指南”鏈接,以了解基於 XML 配置的更多信息)。清單 1 是 這樣的字符串的一個例子:

清單 1. JavaGroups 初始化的配置字符串"UDP: PING: FD(timeout=5000): STABLE:" +
"VERIFY_SUSPECT(timeout=1500):MERGE:" +
"NAKACK:UNICAST(timeout=5000)" +
":FRAG:FLUSH:GMS:STATE_TRANSFER:" +
"QUEUE"

字符串的每個組件用冒號 (:) 分開,分別指定了一個微協議。每個微協議實現一個可組 合的協議特性或質量。實際上,每個微協議由一個相同名稱的 Java 類實現,並且由 JChannel 在運行時加載。許多這些微協議都可以在 org.javagroups.protocol 包中找到, 但是協議設計者可以使用任意的包名稱。棧中指定的每個微協議可以有一個或多個對應的屬 性,可以在初始字符串中的括號中設置這些屬性。協議棧是在運行時自底往上建立的,根據 初始字符串,逐層增加微協議。

運行時可配置、可堆疊的微協議

表 3 顯示了一些經常使用的微協議的描述,其中包括我們的樣例初始化字符串所使用的 一個微協議。要了解單個屬性細節的更多信息,請參見 參考資料部分到"JavaGroups 用戶指南"的鏈接。

表 3. JavaGroups 微協議

微協議名稱 描述 CAUSAL 組中消息的原因排序。實現使用一個矢量時鐘 FD 使用 heartbeat 協議的故障檢測。根據成員關系列表中的排序, Heartbeat 消息被發送到鄰居成員 FD_SOCK 基於 TCP 套接字的故障檢測。基於環 的 ping 被在鄰居成員之間發送。 當所有成員都在同一物理主機上時工作得最好 FD_PID 使用進程 ID 的故障檢測 (本地 JNI 代碼,以獲得所需的 PID)。只能在 同一台主機上 (一個 IP 地址) 工作 FD_PROB 使用隨機算法的故障檢測。組的每個成員發送 heartbeat,並維護其他成 員的 heartbeat 計數 FLOW_CONTROL 流控制實現,限制了消息收據之間發送的消息的最大數量 FLUSH 以一致的方式跨所有成員清除所有的消息。通常是在視圖更改之前執行 FRAG 消息分段和重新裝配(Message fragmentation and reassembly)。確保 較大的消息在沿著棧往下發送之前被分為 FRAG_SIZE大小的段。分段的消息在沿著棧往上發 送之前在接收方被重新裝配 GMS 組管理服務。基於虛擬同步模型管理組成員關系 MERGEMERGE2 合並分離的子組。當網絡由於故障而分離成多個部分時就形成了子組 NACKACK 實現可靠的傳輸。基於消息序列號請求丟失消息的重新傳輸。確保從每個 起始通道發出的消息的正確排序 JMS 將 Java Message Service 用於傳輸。與任何 JMS 實現協同工作 STATE_TRANSFER 實現狀態傳輸協議,使得新成員可以從同等物(coordinator)或所有成員 獲得現有的狀態。需要 FLUSH 微協議在協議棧上 UNICAST 實現可靠的單播傳輸。請求丟失消息的重新傳輸,並確保發出消息的正確 排序 VIEW_ENFORCER 直到接收到第一個 VIEW_CHANGE 才丟棄消息。客戶端只有在成為組成員後 才需要處理消息 STABLE 實現分布式的垃圾收集協議 (也就是說,刪除所有已被所有組成員接收到 的消息) VERIFY_SUSPECT 發送消息以確保以前懷疑的成員已真正崩潰(crashed) UDP 一般用作組消息傳輸的最低層。IP 多播用於組廣播,而 UDP 用於點到點 通信 PING 用於引導成員關系管理。使用 IP 多播 "ping" 消息來定位成 員,然後再請求這些成員加入組中

另外,JavaGroups 還支持一組基於隨機廣播的協議,這些協議可以擴展到非常大的成員 關系 (參見 虛擬同步與隨機廣播)。表 4 顯示了部分列表:

表 4. 基於隨機廣播的微協議

協議 描述 pbcast.GMS 組管理服務,基於隨機廣播 (雜談)。不需要 FLUSH pbcast.FD 基於雜談(gossip) 的被動故障檢測。不發送 heartbeat 消息 pbcast.PBCAST 實現隨機廣播,有規律地以雜談的方式發送到成員關系的一個隨機子集 pbcast.STABLE 實現分布式的垃圾收集協議 (也就是說,刪除所有已被所有組成員接收到 的消息) pbcast.NAKACK 對丟失消息的重新傳輸和消息的順序傳送的否認實現 pbcast.STATE_TRANSFER 將隨機廣播用於狀態傳輸實現。不需要 QUEUE

為了穿過 WAN 和防火牆,JavaGroups 也提供了微協議支持,見表 5:

表 5. 用於穿越 WAN 和防火牆的微協議

TCP 用於取代 UDP 作為最低層傳輸。通過 TCP 連接發送多個單播消息到成員 ,而不是發送多播消息 (不可能)。已經內置了可靠性、FIFO 排序和流控制 TCPPING 使用一組已知的成員來引導通過 TCP 的成員關系管理 TCPGOSSIP 使用一個外部雜談 (參見 參考資料) 服務器,來為成員關系管理的引導定 位成員的初始集合 TUNNEL 當用於代替 UDP 或 TCP 作為最低層傳輸協議時,啟用通過防火牆的隧道 技術。與防火牆外的一個 JavaGroups Router 進程協同工作

利用一個可視化購物車來探討會話復制

為了看看我們是如何將 JavaGroups 用於會話復制的――啟用 Web 層群集――我們可以 創建一個樣例可視化購物車,叫做 JGCart。JGCart 表示 Web 應用服務器管理的單個會話中 的一個 x 線視圖。假設每個應用服務器實例上有上百個這樣的購物車。圖 7 顯示了該可視 化購物車的 GUI:

圖 7. 可視化購物車 (JGCart) 的 GUI

購物車 GUI 和事件流

在購物車的上面是一個產品目錄。通過選擇標簽,我們可以選擇任何種類的產品。點擊商 品邊上的 Buy 按鈕,就可以將該商品添加到下面的購物車中。購物車清楚地顯示了我們已經 訂購的商品――包括價格和數量――並且計算出了總價格 (單價乘以所訂購的數量)。這是應 用服務器內單個購物車會話的可視化表示。在任何時候,一個應用服務器可能在內存中管理 有許多這樣的會話。我們可以使用 JavaGroups 以一種易於編程和維護的方式來啟用這樣的 會話的復制。

圖 8 顯示了我們的 JGCart 應用中的 GUI 組件的層次。整個 GUI 是使用 Swing GUI 庫 創建的。

圖 8. JGCart 的 GUI 組件裝配

GUI 的上半部分是 CatalogUI 組件。CatalogUI 是一個 JPanel 組件,其中帶有一個 受 管 JTabbedPane ,這個 JTabbedPane 顯示一系列 CatalogItem 組件。每個 CatalogItem 都是 JPanel 組件,各自帶有一個 JButton 和兩個 JLabel 組件。JButton 就是 Buy 按鈕 ,而兩個 JLabel 顯示每個商品的描述和價格。

CatalogUI 通過提供一個 setOrderListener() 方法,支持 Buy 事件的轉發。 OrderListener 接口用於每當一個 Buy 按鈕被點擊時轉發一個來自 CatalogUI 組件的 OrderEvent。圖 9 顯示了事件轉發動作:

圖 9. JGCart 中的事件流

在圖 8 中 GUI 的下半部分有一個 OrderList 組件。該組件是一個 JPanel, 其中帶有 一個 受管JTable ,這個JTable在任何時候都會顯示購物車的內容。受管 JTable 有一個定 制的模型 (包含顯示的數據),就像由我們的 OrderTableModel 類實現的一樣。該定制模型 確保我們在會話中――在 CartState 類的一個實例中――維護的狀態與受管 JTable 中顯示 的狀態同步。通過使用 OrderTableModel.changeData() 方法,我們可以在任何時候更新模 型中的數據 (並顯示這些數據) 。

編程 GUI 和連接事件流

在清單 2 中 (以紅色高亮顯示),我們可以看到如何通過 JGCart 類的 CreateUIandPrepChannel() 和 addOrderItem() &#160&#160 方法將 CatalogUI 組 件連接到 OrderList 組件。

注意, addOrderItem() 方法不是由於 Buy按鈕點擊事件而直接被調用的。相反,點擊 Buy 按鈕會產生一個 到群集的所有成員 (包括發送消息的成員) 的AddItemMessage 消息的 廣播。實際上, addOrderItem() 方法是在該消息的處理期間,通過 MessageListener 接口 (由 JGCart 類實現)的 receive() 方法調用的。這將有效地復制購物車中的所有更改到該組 的所有成員。

如果您對所有 GUI 類的詳細操作感興趣,請參見 參考資料部分,以下載源代碼。

清單 2. 在 JGCart 中創建 GUI 及轉發事件private void CreateUIandPrepChannel() {
  mainFrame=new JFrame();
   ...
   subPanel = new JPanel();
   subPanel.setLayout(new GridLayout(2,1));
   <span class="rboldcode">catUI = new CatalogUI();
   catUI.addOrderListener(this);
   orderModel = new OrderTableModel(Arrays.asList(data));</span>
   ordList = new OrderList(orderModel);
   subPanel.add(catUI);
   subPanel.add(ordList);
   mainFrame.getContentPane().add("Center", subPanel);
   ...
  
  }
 ...
 private void addOrderItem(String desc, Integer price) {
 <span class="rboldcode">   cstate.addOrderItem(desc, price);
   orderModel.changeData(cstate);</span>>
}

測試可視化購物車

要觀察該應用的會話復制行為,請執行一下步驟:

通過使用 run.bat 批處理文件 (在代碼分布的 src 目錄下),在您的系統上啟動 JGCart 的一個實例。您需要編輯 run.bat 文件,以指定 JavaGroups 庫位於哪裡。

在同一 LAN 上的另一台 PC 上啟動另一個實例 (如果您不是工作在 LAN 上,則在同一台 PC 上啟動另一個實例)。

在第一個實例上,單擊目錄,然後單擊幾件商品的 Buy按鈕。注意,會話狀態更改馬上被 復制到其他機器。

圖 10 演示了由&#160 JGCart 表示的兩個復制的會話。注意它們之間如何保持同步 。

圖 10. 復制的 JGCart 會話

如果您在 LAN 上不止有兩台機器,您就可以容易地將該實驗群集擴展到更多的機器―― 只要在這些機器上啟動 JGCart 的新實例。

現在假設該購物車會話是運行在一個應用服務器內,而該服務器硬件崩潰了。通過關閉第 一個 JGCart 實例,我們可以模擬這一場景。當然,很容易看到,通過發送請求到會話的第 二個實例,我們可以繼續購物。以群集的術語來說,這是一個 fail-over。在群集中的多台 服務器之間的硬件崩潰事件中幸免於難的能力,確保了服務的高可靠性。

實際上,即使沒有系統崩潰,購物車請求也可以在任何時候被定向到服務器 A 或服務器 B,因為會話存在於這兩台服務器上並可以在任何一台服務器上被更改。入站請求可以被定向 到當前具有最低工作負載的服務器 (叫做 負載平衡),這對於購物者是透明的。正如您可以 看到的,使用 JavaGroups 進行的跨一群機器的 Web 層會話復制可以提供高可用性的服務, 並且具有 fail-over 和負載平衡的可能性。

會話復制中的問題

正如我現在將要闡述的,會話復制中會有問題。啟動 JGCart 的另一個實例 (在 LAN 中 的另一台機器上或者在同一台機器上)。現在,返回到初始實例,並且又添加一些商品。圖 11 演示您應該看到的東西:

圖 11. 不同步復制的 JGCart 會話

盡管到購物車會話中的所有添加動作仍然會被復制到第二個實例,但是這兩個實例完全是 不同步的。當然,這一問題的原因是,我們已經在第一個購物車中放入了幾件商品之後才啟 動第二個實例。一開始我們沒有遇到這個問題,是因為我們是在同一時間啟動的所有復制會 話。

在群集實現中,強調群集中的機器都在同一時間啟動是不合理的。實際上,我們應該能夠 在任何時候添加或刪除機器。這就要求新加入群集的機器能夠從復制的會話那裡請求“ 當前狀態”。沒有什麼奇怪的,JavaGroup 的 JChannel 特別為此提供了一個 STATE_TRANSFER 微協議。參見 參考資料 部分,以了解有關 STATE_TRANSFER 微協議實現的 詳細信息。

利用 JavaGroups 編碼狀態傳輸邏輯

為了在 JGCart 應用中加入狀態傳輸功能,我們必須添加執行以下任務的代碼:

1. 設置 Channel 選項以響應 GET_STATE 請求。默認情況下,來自 STATE_TRANSFER 協 議的任何 GET_STATE 事件不會被傳播到應用,以簡化典型的客戶端實現。在我們的案例中, 我們想要接收 GET_STATE 事件。清單 3 顯示了我們如何設置該選項 (以紅色高亮顯示)。這 是在 PrepareChannel() 方法內完成的。

2. 在連接到通道之後,調用 JChannel 的 getState() 方法,以從群集獲得當前狀態 ( 因為會話是在群集中相等地復制的,因此可以從任何成員獲得當前狀態),見清單 3 (以綠色 高亮顯示)。同樣,它也是 PrepareChannel() 方法的一部分。

清單 3. 為狀態傳輸准備通道private void PrepareChannel() throws Exception  {
  Channel=new JChannel(props);
<span class="rboldcode">  Channel.setOpt (Channel.GET_STATE_EVENTS, Boolean.TRUE);</span>
  System.out.println("Connecting to " + groupname);
  Channel.connect(groupname);
  padapter =new PullPushAdapter(Channel, this, this);
      
<span class="gboldcode">  Channel.getState (null,0);</span>
}

3. 從微協議棧接收到 SET_STATE 請求之後, PullPushAdapter 實例將回調 MessageListener 接口的 setState() 方法。我們需要實現該方法來設置我們的狀態。清單 4 顯示了這一實現。這裡,我們只是設置了私有 cstate 變量,然後利用 cstate 更新 TableModel。這將自動更新 GUI 視圖,因為 Swing 庫例程將同步 GUI 視圖和 TableModel 。

清單 4. 在狀態傳輸期間設置 JGCart 的狀態  public void setState(Object state) {
        if (state == null) {
             System.out.println("-- PullPush callback:
              initial SETSTATE with null arg.");
             cstate = new CartState();
             }
          else {
          // set our local state
          cstate = (CartState) state;
         }
          orderModel.changeData(cstate);
   }

4. 在接收到 GET_STATE 請求之後――一般是從加入群集的新成員處接收到――我們需要 返回當前狀態。PullPushAdapter 類將回調 MessageListener 接口的 getState() 方法。清 單 5 顯示我們對了這一方法的實現。我們只是復制了一份狀態,然後將它作為對象返回。

清單 5. 處理 GET_STATE 請求public Object getState() {
 System.out.println("-- PullPush callback: GetState has
   been called!");
 return cstate.Copy();
 }

值得注意的是,必須對狀態做一份 深拷貝(每個引用對象的逐個成員的拷貝),因為狀態 在線路上傳輸之前, STATE_TRANSFER 協議有可能保留一會狀態。如果狀態引用的任何對象 在這時被修改了,傳輸的狀態就會變得不一致。實際上,因為深拷貝操作本身要花一定的時 間,所以在復制期間要防止對狀態的並發訪問,以確保狀態操作的同步。

5. 最後,我們需要在 JGCart 啟動期間調用 getState()。清單 3 中以綠色高亮顯示的 一行負責這一動作,並且是 PrepareChannel() 方法的一部分。注意 getState() 調用是異 步的。它只是啟動 STATE_TRANSFER 協議以從群集獲得狀態,但是它立即返回。 PullPushAdapter 類對 MessageListener 接口的 setState() 的回調將在以後某一時間發生 。

清單 3 到 5 中的代碼都在源代碼中 (參見 參考資料),但是都被注釋掉了。要獲得狀態 傳輸支持功能,請刪除注釋並重新編譯。

有了 STATE_TRANSFER 支持之後,我們就作好了再次嘗試 JGCart 群集模擬的准備。首先 ,啟動一個 JGCart 實例,然後添加幾件商品到購物車。現在通過啟動另一個 JGCart 實例 來模擬添加新機器到群集。該實例現在啟動了,就像第一個實例一樣,所有的商品都在購物 車中。在它啟動之後,我們可以使用任何一個 JGCart 來繼續購物。JavaGroups 的狀態傳輸 協議允許我們在任何時候添加新機器到群集中。

當然,如果需要傳輸的狀態非常大,那麼將它通過線路發送給每台加入群集的新機器是不 切實際的,甚至是不可能的。當機器經常性地加入和離開群集時尤其如此。幸運的是,J2EE Servlet/JSP 容器級別的實現通常涉及到較低的成員關系計數,並且成員關系很少改變 (例 如,機器崩潰或者拿到群集外面去維護)。

廉價機器群集可以為部署 Web 應用和 Web 服務提供一個可伸縮的、高度可用的平台。然 而,這種群集所需的網絡軟件通常是針對於某一特定應用的,並且是難以編寫和測試的。 JavaGroups 是一個開放源代碼的分布式系統工具,通過提供以下馬上可以部署的、高級別的 特性,對於這一難題可以有所幫助:

組成員關系管理。

基於多播和單播消息的組通信。

狀態傳輸協議。

功能分布式數據結構。

一些可重用的、經常使用的通信編碼模式組成的庫。

利用 JavaGroups 的特性,我們創建了群集購物車 Web 應用(JGCart)的會話復制機制 的一個可視化演示。通過對 JGCart 進行實驗,我們看到了會話復制如何才能改善 Web 層應 用的可用性和可伸縮性。

通過使用 JavaGroups 來處理群集解決方案的組通信、狀態傳輸和數據復制等方面,設計 者可以將精力集中於其他特定於應用的需求。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved