Geronimo 中的集群的歷史
在一個下雪的清晨,我乘公車來到我在 Calgary 的辦公室,滿腦子是當天我要與 Julian(Jules) Gosnell 進行的 VoIP 電話談話,他是 Codehaus 的 WADI 項目的創建者,Apache Geronimo 應用服務器項目的共同創立者,也是這個項目的集群解決方案的提交者中的一員。開始的時候,我對這次采訪沒有自信,但是當采訪開始之后,就很清楚了:Jules 對這個主題很熱心,采訪進展迅速,持續了一個多小時。本文把重點放在這個主題的 第一篇文章 忽視了(或者簡要地提到過,但是沒有進行完整的介紹)的 Geronimo 集群工作的一些具體細節上。
所以,雖然雪花在我的辦公室外不斷飄落,飄落在英國倫敦以南 45 分鐘車程的一個小房子旁邊的木屋辦公室外的花園里,Jules 還是從他的角度向我談起了 Geronimo 集群的歷史。
通往 WADI 之路
首先,我問 Jules 他最初是如何進入 Java™ Enterprise Edition 世界的。他提到他那時在倫敦的一家銀行工作,編寫 Java servlet 來替代用 C++ 編寫的老系統。正是通過這個工作,他了解到 Greg Wilkins 和 Jan Bartel 高質量的開發工作,這兩人是輕量級 HTTP 服務器和名為 Jetty 的 servlet 運行程序的創建者和開發人員。Jules 對 Jetty 的興趣,使他參與了一些 JBoss 應用服務器開發工作以集成 Jetty。之后,他還參與了為 Jetty 實現集群的 Web 會話實現。就在 Geronimo 項目開始之后不久,在 2003 年 8 月,關于為這個項目實現集群功能的討論開始了。
我問 Jules 為什么 WADI 是在 Codehaus 存儲庫中開發的,而不是在 Geronimo 項目的源代碼樹中開發的。他說:最初,James Strachan 建議構建一個通用的集群 API(ActiveCluster),以支持 Geronimo 或需要這類功能的其他系統的集群抽象。Codehaus 被選中作為一個中立于許可的基礎。ActiveCluster 的目的是與既是 LGPL(較少通用公共許可)許可的又是 ASF(Apache 軟件基金會)許可的實現聯系起來,因而他們想在這個項目中涉及兩個社區。
Jules 解釋說由于他的興趣和經驗主要是在 Web 會話上,所以他的重點很快從 ActiveCluster 的基本集群問題轉移到與他的興趣領域更相關的問題上,從而導致 2004 年 4 月在 Codehaus 上創建了 WADI 項目(最初是 Web 應用程序分布式基礎設施,現在是 WADI 應用程序分布式基礎設施,因為 WADI 已經成為更加通用的會話管理框架。請注意傳統的遞歸首字母縮寫)。James 繼續開發 ActiveCluster,而他對集群緩存的興趣導致他在 Codehaus 上創建了 ActiveSpace 項目。Jules 說,這個項目像 WADI 一樣,也利用了 ActiveCluster API。
很明顯,在應用服務器領域有一些在潛心研究的隊伍,這是我們在理解為什么編寫了這些項目時需要考慮的因素。他們要解決什么問題呢?
![]() ![]() |
![]()
|
集群策略
簡而言之,集群就是用于解決許多基本問題的策略。我們介紹其中兩個最重要的問題,以及如何在 Geronimo 應用服務器中解決它們。
可用性和可伸縮性問題
第一個問題的正式叫法是可用性(availability)問題。隨著企業應用程序變得越來越流行,任何停機時間都變得非常值得注意。通過添加更多的計算機,在出現問題時接手工作,可以提高系統的可用性。這叫做故障轉移(failover) —— 這是實現高可用性的機制。第二個問題的正式叫法是可伸縮性(scalability)問題,這個問題隨著使用系統的人數增多而出現。有些計算機可能變得過載,所以如果增加更多的計算機,并在解決方案中增加負載均衡能力,讓每臺機器承擔一部分負載,就會在整體上形成更好的響應時間。
會話狀態
要讓用戶能夠與應用程序進行交互,通常要在服務器上保存關于這個用戶對應用程序已經做了什么的信息。這個信息叫做會話狀態,是有用的應用程序的重要部分。例如,假設登錄到一個在線商店,在購物車中添加了幾個商品,想要結帳購買商品。如果沒有會話狀態,那么這個操作會很難處理。Jules 指出,會話狀態讓用戶可能與遠程應用程序(例如 Web 應用程序)進行對話。如果沒有會話,那么所做的每個請求都與前一個請求沒有關系。他說,在效果上,就像與一個人談話時,這個人每回答一句,就把前面的談話全部遺忘。
會話親和力
所以,在應用程序中保存會話狀態是非常重要的。通常,在集群的情況下,會話狀態保存在集群中的一臺計算機上,并且負載均衡器總是把用戶的請求重定向到保存他(或她)的會話的機器上。傳統上,這叫做會話親和力(session affinity)。但是,這根本沒有解決可用性問題。如果保存會話的計算機崩潰了,購物者將感受到糟糕的體驗。許多開放源碼解決方案,每次在會話發生變化時,都把會話的備份拷貝廣播到集群中的其他每個節點。如果其中任何一臺機器停機,會話仍然是安全的。因為可以在這些備份拷貝中找到一個,并重新激活它。
這個理論適用于小型部署(三到四個節點),但是 Jules 告訴我該方法也不能伸縮。每個額外節點都需要額外的內存來保存集群中每個節點的備份,所以很快就會用光內存。而且,對會話的每次變化都會形成對每個節點的操作,因為節點必須處理進來的消息更新,讓會話的備份拷貝與變化一致。隨著節點、會話和用戶數量的增加,每個節點花在維持備份拷貝上的工作量也會增加,從而只留下更少的時間去為用戶請求服務。這會造成性能的下降:越來越多的時間和帶寬花在了在集群中維護會話備份的一致性,而不是用來處理用戶請求。
靜態分區
有些開放源碼解決方案試圖利用叫做靜態分區 的策略來彌補這個可伸縮性問題。這種策略把集群分成比較小的集群,以避免達到內存和帶寬的極限。但是,Jules 指出靜態分區不僅配置起來更復雜,而且也會影響可用性。最后導致要管理許多比較小的集群,而比較小的集群只具有比較低的可用性,因為可用性取決于集群的大小。
動態分區
因為 Jules 親身看到了與靜態分區有關的問題,也為自己不能用開放源碼組件為客戶提供更好的解決方案而感到沮喪,所以 WADI 從開始時就采用了動態分區的方式。他還認識到這也是他為 Geronimo 做貢獻的方式。隨著 WADI 項目在 Codehaus 上的崛起(這是主項目管理員的評價),他的工作朝著解決會話狀態分布問題的方向前進,形成了一個更好的集群分區解決方案,叫做動態分區。
![]() ![]() |
![]()
|
WADI 解決方案
Jules 有一個問題要解決,他花了兩年中相當多的時間來解決這個問題,從 2004 年 4 月他把初始的 WADI 源代碼導入 Codehaus 時開始。在這兩年中,WADI 的源代碼從無到有發展到大約 44,000 行代碼。我最初看了 Codehaus 存儲庫中的 WADI,對于這個項目的歷史有了些理解,但是對于 Jules 表示感謝的那些幫助完成該項目的人,這個理解當然沒有提供正確的認識。
他告訴我,設計從一開始,首先受到了 James Strachan 和 Hiram Chirino 的影響,因為 WADI 利用了 ActiveCluster 和 ActiveMQ;其次受到了編寫 Jetty 的 Greg Wilkins 和 Jan Bartel 的影響,因為 WADI 與 Jetty 連接。Greg 的朋友 Simone Bordet(MX4J 和 LiveTribe),在項目早期就對它產生了興趣。Gianni Scenini 與 Jules 一起為一個客戶工作,他花了許多時間與 Jules 探討 WADI 應當如何工作。在 2005 年底,Jeff Genender、Bruce Snyder、James Goodwill 和 Bill Dudney 加盟進來。就在這時,Gianny Damour 開始在 WADI 中集成 OpenEJB,從而向它提供一個有狀態會話 bean 解決方案。
分布式散列映射
Jules 對我解釋道:在 WADI 中,會話信息分布在集群的節點間,每個會話的位置保存在索引中。索引被作為一種名為分布式散列映射(distributed hash-map)的分布式數據庫被分解并在節點間共享,這樣每個節點就都可以訪問每個會話的位置,這些信息要小得多,從而降低了整體的內存負載。
不用在每個節點上都擁有每個會話的一個備份,應用程序部署器(應用服務器中負責啟動并運行應用程序的部分)決定它們需要的安全級別,并配置固定數量的備份(通常一個或兩個)。不論集群變得多大,制作的備份數量都不超過這個數目。這就避免了前面提到的內存限制。而且,因為會話變化時需要更新的會話備份更少,所以 WADI 也避免了前面討論的服務性能降低的問題。
在正常的操作下,由于在負載均衡器上開啟了會話親和力,所以請求總是路由到擁有相關會話的節點,而請求的處理只涉及這個節點,沒有與集群相關的其他開銷。但是,在許多其他情況下,無法維持會話親和力,例如:在機器異常地崩潰時,或者再極端些,在機器完全關機時。在這些情況下,WADI 會使用它的會話位置映射,把進入的請求重新定位到會話的新位置,或者安排會話從這個新位置遷移到請求到達的節點。
在基于 WADI 的集群的操作中,隨時都可以動態地添加或移除計算機。添加到集群的計算機就把它們的內存和資源添加到池中,負責整體會話位置映射的部分責任,并形成與其他集群成員的復制伙伴關系,從而使它們也可以接收會話信息的復制拷貝。這些復制的會話是針對集群中計算機意外崩潰的保險措施。機器也可以在控制下進行關機。當發生這種情況時,會話被 “疏散” 到其他幸存的計算機。在內存不足的情況下,沒有使用的會話就被寫入持久存儲中,這樣當用戶在會話到期之前回到計算機上時,可以檢索到這些會話。
存儲復制的拷貝
為了選擇把復制的拷貝存儲到哪兒,采用了一種可插入的選舉機制來選擇復制伙伴:距離遠的機器、負載輕的機器或者到保留會話的機器的網絡連接更快的機器,被首選進行復制。有些啟發式方案可能會花更長的時間來選擇更好的復制伙伴,例如為機房中同一架子上的機器添加一個處罰因子,這樣,如果整個架子上的機器因為電源故障全部停掉,會話可能已經復制到了另一個架子上的機器,因此可能幸免于難。
會話數據與業務數據有不同的特征。業務數據通常存在于數據庫中,可以在需要的時候從數據庫中讀取出來。會話數據是臨時的,通常短期存在,需要在頁面的呈現過程中被迅速地訪問到。會話數據必須以備份的形式迅速進行復制。Jules 告訴我 Gianny Damour 正在處理這個解決問題。
會話平等
Geronimo 有許多不同的會話 —— Web 應用程序會話、企業 JavaBean(EJB)無狀態會話 bean、Web 服務會話,等等。Jules 正在與 Geronimo 項目的人員合作,把一些不同的技術與 WADI 合并,從而讓 Geronimo 用同樣的方式對待所有會話。它還會把相關會話組織在一起,這樣,與同一用戶關聯的 Web 和 EJB 層可以保存在集群中的同一位置;而且,當需要移動會話數據時,相關會話將會一起移動。
保持緩存一致
與應用服務器中的集群有關的另一個領域是緩存。實體 bean 是通常保存在數據庫中的數據的抽象。為了提高應用程序的性能,數據庫中的數據被緩存在應用服務器中。對數據的修改被寫回數據庫,數據的緩存版本或者被拋棄(這樣在下一個請求時再檢索它們),或者緩存的數據也做修改,以保持一致。在單一計算機上,這種方式工作得很好,但是在進入集群模型時,重要的是緩存的數據要在所有節點間保持一致。這通常叫做緩存一致性問題。
所以,Geronimo 集群解決方案看起來更像 圖 1 中的結構。
為了解釋這個圖表,先從信息源所在的底部開始。信息可以來自數據庫或來自參與對等信息共享的集群中的其他節點。數據庫信息使用 JCache 實現進行映射,緩存在 ActiveSpace 的集群緩存中,并通過對象關系映射提供給應用程序。在這個圖的另一邊,WADI 用 ActiveCluster 來管理應用服務器集群中存在的所有不同的用戶會話數據,包括 Web 會話、無狀態會話 bean、Web 服務會話,等等。ActiveSpace 也使用 ActiveCluster 來保持緩存內容(例如實體 bean、部署和 JNDI 目錄信息)的一致性。
所以可以看出,WADI 是解決方案的一部分,而不是整體解決方案。其他項目用來滿足集群的需求。還可以看出,Codehaus 在這么多項目的早期階段中扮演了重要的角色,它允許高度關注這些項目的人們可以在創新的和友好的環境中進行開發?,F在,在開發了多年之后,這些項目正在尋找自己的地位,從而在 Geronimo 項目中占據各自的位置。
![]() ![]() |
![]()
|
結束語
Geronimo 的集群解決方案仍在積極地開發,但是我希望通過這篇文章,您可以看到 Geronimo 的開發人員努力解決的一些問題??梢韵胂?Geronimo 完成時我們將享受的好處,所以我可以列出 Geronimo 的集群功能:
最后,Jules 希望我向讀者說明:開放源碼軟件是整個開發人員社區協作的成果,雖然這篇文章中只提到了少數幾個人,但是他希望感謝更多的人對 Geronimo、WADI 和相關項目作出的貢獻。