序言 html
愈來愈多的關鍵應用運行在J2EE(Java 2, Enterprise Edition)中,這些諸如銀行系統和帳單處理系統須要高的可用性(High Availability, HA),同時像Google和Yahoo這種大系統須要大的伸縮性。高可用性和伸縮性在今天高速增加的互鏈接的世界的重要性已經證明了。eBay於 1999年6月停機22小時的事故,中斷了約230萬的拍賣,使eBay的股票降低了9.2個百分點。java
J2EE集羣是用來提供高可用性和伸縮性服務,同時支持容錯處理的一種流行的技術。可是,因爲J2EE規範缺少對集羣的支持,J2EE供應商實現集羣的方法也各異。這給J2EE架構師和開發人員帶來了不少困難。如下是幾個常見的問題:算法
理解這些限制和要素的最佳方法是學習他們的實現方式。數據庫
基本術語 設計模式
在咱們討論不一樣的集羣實現以前,先談談幾個概念。這有助於理解不一樣的J2EE集羣產品不一樣的設計結果和概念:瀏覽器
伸縮性(Scalability):緩存
在一些大的系統中,預測最終用戶的數量和行爲是很是困難的,伸縮性是指系統適應不斷增加的用戶數的能力。提升這種併發會話能力的一種最直觀的方式就增長資源(CPU,內存,硬盤等),集羣是解決這個問題的另外一種方式,它容許一組服務器組在一塊兒,像單個服務器同樣分擔處理一個繁重的任務。安全
高可用性(High availability):服務器
單一服務器的解決方案並非一個健壯方式,由於容易出現單點失效。像銀行、帳單處理這樣一些關鍵的應用程序是不能容忍哪怕是幾分鐘的死機。它們須要這樣一些服務在任什麼時候間均可以訪問並在可預期的合理的時間週期內有響應。集羣方案經過在集羣中增長的冗餘的服務器,使得在其中一臺服務器失效後仍能提供服務,從而得到高的可用性。網絡
負載均衡(Load balancing):
負載均衡是集羣的一項關鍵技術,經過把請求分發給不一樣的服務器,從而得到高可用性和較好的性能。一個負載均衡器能夠是從一個簡單的Servlet或 Plug-Ins(例如一個Linux box利用ipchains來實現),到昂貴的內置SSL加速器的硬件。除此以外,負載均衡器還需執行一些其餘的重要任務,如「會話膠粘」讓一個用戶會話始終存在一個服務器上,「健康檢查」用於防止將請求分發到已失效的服務器上。有些負載均衡器也會參與咱們下面將要談到「失效轉移」過程。
容錯(Fault tolerance):
高可用性意味着對數據正確性的要求不那麼高。在J2EE集羣中,當一個服務器實例失效後,服務仍然是有效的,這是由於新的請求將被冗餘服務器處理。可是,當一個請求在一個正在失效的服務器中處理時,可能獲得不正確的結果。無論有多少個錯誤,容錯的服務應當能確保有嚴格的正確的行爲。
失效轉移(Failover):
失效轉移是集羣中用來獲取容錯能力的另外一項關鍵的技術。當一個結點失效後,經過選擇集羣中的另外一個結點,處理將會繼續而不會終止。轉移到另外一個結點能夠被顯式的編碼,或是經過底層平臺自動地透明地路由到另外一個服務器。
等冪方法(Idempotent methods):
等冪方法是指這樣一些方法:重複用相同的參數調用都能獲得相同的結果。這些方法不會影響系統狀態,能夠重複調用而不用擔憂改變系統。例如:getUsername()就是等冪的,而deleteFile就不是。當咱們討論HTTP Session失效轉移和EJB失效轉移時,它是一個重要的概念。
什麼是J2EE集羣
一個天真的問題,不是嗎?但我仍要用幾句話和圖來回答它。一般,J2EE集羣技術包括"負載均衡"和"失效轉移"。
如圖1所示,負載均衡意味着有許多客戶端向目標對象同時發出請求。負載均衡器在調用者和被調用者之間,分發請求到與原始對象相同的冗餘對象中。伸縮性和高可用性就是這樣獲得的。
如圖2所示,失效轉移與負載均衡不一樣。有時客戶端會連續發請求到目標對象,若是請求中間目標對象失效了,失效轉移系統將檢測到此次失敗,並將請求重定向到另外一個可用的對象。經過這種方式能夠得到容錯能力。
若是你想知道更多的有關J2EE集羣的知識,你就會問到一個基本的問題,「什麼對象能夠集羣?」和「在個人J2EE代碼中哪裏會發生負載均衡和失效轉移呢?」。這些都是用來理解J2EE集羣的很是好的問題。實際上,並非全部的對象都能被集羣的,而且負載均衡和失效轉移並非在J2EE代碼全部地方都能發生。看看下面的例子代碼:
在Class A的bussiness()方法中,instance1能夠負載均衡嗎?或是當其失效,能夠失效轉移到其餘B的實例上嗎?我想是不行的!對負載均衡和失效轉移來講,必需要有個攔截器在調用者和被調用者之間分發或重定向請求到不一樣的對象上。Class A和Class B的實例是運行在一個JVM中緊密耦合的,在方法調用間加入分發邏輯很是困難。
什麼類型對象能夠被集羣?——只有那些能夠被部署到分佈式拓樸結構中的組件。
在個人J2EE代碼中,什麼地方會有負載均衡和失效轉移?——只在你調用分佈式組件的方法時。
在如圖4所示的分佈式環境中,調用者和被調用者被分離在有明顯邊界的不一樣的運行容器中,這個邊界能夠是JVM,進程和機器。
當目標對象被客戶端調用時,目標對象的功能是在容器中運行的(這就是爲何咱們說它是分佈式的緣由)。客戶端和目標對象經過標準的網絡協議通訊。這些特性就爲一些機制提供了機會能夠介入到方法調用之間實現負載均衡和失效轉移。
如圖4,瀏覽器經過HTTP協議調用JSP對象,JSP運行在WEB服務器中,瀏覽器只須要返回結果而不關心它是怎麼運行的。在上述場景中,一些東西就能夠在瀏覽器與WEB服務器之間實現負載均衡和失效轉移的功能。在J2EE平臺,分佈式技術包括:JSP(Servlet),JDBC,EJB,JNDI,JMS,WEB Service等。負載均衡和失效轉移就發生在這些分佈式方法被調用時。在後續部分咱們將詳細討論這些技術。
4 WEB層集羣實現
WEB層集羣是J2EE集羣的重要且基本的功能。WEB集羣技術包括WEB負載均衡和HTTP Session失效轉移。
4.1 WEB負載均衡
J2EE提供商實現WEB負載均衡有許多方式。基本上,都一個負載均衡器被插入到瀏覽器和WEB服務器之間,以下圖所示。
圖 5 WEB負載均衡
負載均衡器能夠是一臺硬件,如F5負載均衡器,或僅僅是另外一臺有負載均衡Plug-Ins的WEB服務器,一個簡單的帶ipchains的Linux box能夠很好的實現負載均衡。無論採用哪一種技術,負載均衡器都有如下特性:
4.1.1 實現負載均衡算法
當客戶請求到來時,負載均衡器須要決定將如何分發到後臺服務器。流行的算法是Round-Robin、Random和Weight Based。負載均衡器盡力使每臺服務器實例都得到相同的負載,可是上述算法沒有一個能夠得到理想的負載相同,由於它們僅僅是依據發送到特定服務器的請求的個數。一些精密的負載均衡器實現了特殊的算法。它在分發請求以前將檢測服務器的工做負載。
當一臺服務器失效了,負載均衡器應當檢測出失效並再也不將請求分發到這臺服務器上。一樣,它也要檢測服務器是否恢復正常,並恢復分發請求。
幾乎全部的WEB應用程序都有一些會話狀態,多是簡單的記住用戶是否登錄,或是包含你的購物車信息。由於HTTP自己是無狀態的,會話狀態應當存在服務器的某個地方並與你當前瀏覽會話相關聯,這樣當你下次再請求相同WEB應用程序的頁面時能夠很容易的從新獲取。當負載均衡時,最佳的選擇就是將特定的瀏覽器會話分發到上次相同的服務器實例中,不然,應用程序可能不能正確工做。
由於會話狀態存儲在特定WEB服務器的內存中,「會話膠粘」對於負荷均衡很是重要。可是,若是其中某臺服務器實例由於某種緣由失效了(好比關機),那麼這臺服務器的會話狀態將要丟失。負載均衡器應當檢測到這個失效並再也不將請求分發給它,但這些請求的會話狀態都由於存放在失效的服務器中而丟失了全部信息,這就將致使錯誤。會話的失效轉移所以而生。
4.2 HTTP Session失效轉移
幾乎全部流行的J2EE供應商都在他們的集羣產品中實現了Http Session失效轉移,用來保障當某臺服務器失效後會話狀態不會丟失,使客戶端請求能被正確處理。如圖6所示,當瀏覽器訪問有狀態的WEB應用程序(第 1 ,2步),這個應用程序可能在內存建立了會話對象用於保存信息以供後面的請求使用,同時,發送給瀏覽器一個惟一的HTTP Session ID用於標識這個會話對象(第3步),瀏覽器將這個ID保存Cookie中,並當它下次再請求同一WEB應用程序的頁面時,會將Cookie發還給服務器。爲了支持會話失效轉移,WEB服務器將在必定的時候把會話對象備份到其餘地方以防止服務器失效後丟失會話信息(第4步)。負載均衡器檢測到這個失敗(第5,6步),並將後續的請求分發到裝有相同應用程序的服務器實例中(第7步),因爲會話對象已經備份到其餘地方了,這個新的服務器實例能夠恢復會話(第8步)正確地處理請求。
圖 6 HTTP Session失效轉移
爲了實現上述功能,HTTP Session失效轉移將帶來如下問題:
如上所述,HTTP Session ID用於在特定的服務器實例中標識惟一的內存會話對象,在J2EE平臺,HTTP Session ID依賴於JVM實例,每一個JVM實例擁有幾個應用程序,每一個應用程序都爲不一樣的用戶管着許多會話,HTTP Session ID是在當前JVM實例用於訪問相關會話的關鍵。在會話失效轉移的實現中,要求不一樣的JVM實例不能產生兩個相同的HTTP Session ID,這是由於當失效轉移發生時,一個JVM的會話將要備份並恢復到另外一箇中,這樣,必須創建全局HTTP Session ID產生機制。
如何備份會話狀態是區別J2EE服務器好壞的關鍵因素。不一樣的供應商有不一樣的實現,在後續部分我再詳細解釋。
會話的備份是消耗性能的,包括CPU,內存,網絡帶寬和寫入磁盤和數據庫的I/O,備份的頻率和備份對象的粒度將嚴重影響性能。
4.2.1 數據庫備份方式
幾乎全部的J2EE集羣產品都容許選擇將你的會話對象經過JDBC備份到關係數據庫中。如圖7所示,這種方式可讓服務器實例很是簡單的在正確的時間序列化會話內容並寫到數據庫中。當發生會話轉移時,另外一臺可用的服務器接過已失效的服務器工做,從數據庫中恢復全部的會話狀態。序列化對象是關鍵點,它使得內存會話數據能夠持久化和傳輸。要了解更多有關Java對象序列化知識,請參考http://java.sun.com/j2se/1.5.0/docs/guide/serialization/index.html 。
圖 7 備份會話數據到數據庫
因爲數據庫交易是很是昂貴的,這種方法主要缺點是當在會話中保存大量的或大的對象時限制了伸縮性,大多數使用數據庫會話持久化的服務器產品都提倡儘可能減小用HTTP會話存儲對象,但這限制了你的應用程序的架構和設計,特別是你要使用HTTP會話緩存用戶數據時。
數據庫的方式也有一些優勢:
4.2.2 內存複製方式
由於性能的緣由,一些J2EE服務器(Tomcat,Jboss,WebLogic,WebSphere)提供了另外一種實現:內存複製
圖 8 對會話狀態進行內存複製
基於內存的會話持久化將會話信息保存在一臺或是多臺備份服務器中,而不是保存數據庫中(如圖8)。這種方式由於性能高而很是流行。同數據庫方式相比,直接在原服務器和備份服務器之間網絡通訊是很是輕量的。同時注意在使用方式中,數據庫方式中的「恢復」階段是不須要的,由於在備份後,全部會話數據都已經存在備份服務器的內存中了,已經能夠處理請求。
「Java Groups」是當前Tomcat和Jboss集羣所使用的通訊層。Java Groups是用於實現可靠組通訊和管理的工具包。它提供了諸如「組成員協議」和「消息廣播」等核心特性,這些都對集羣的工做很是有用。有關Java Groups的信息,請參考:http://www.jgroups.org/javagroupsnew/docs/index.html 。
4.2.3 Tomcat方式:多服務器複製
內存複製也存在許多不一樣的方式,第一種方法就是將會話數據複製到集羣中的全部結點,Tomcat5採用的就是這種方式。
圖9 多服務器複製
如圖9所示,當一個服務器實例的會話改變後,將備份到其餘全部的服務器上。當一臺服務器失效後,負載均衡器能夠選擇其餘任何一臺可用的服務器實例。但這種方式限制了伸縮性,若是集羣中有不少的服務器實例,那麼網絡通訊的代價就不能被忽略,這將嚴重下降性能,而且網絡也將成爲系統的瓶頸。
4.2.4 WebLogic,Jboss和Websphere的方式:對等服務器複製
因爲性能和伸縮性的緣由,WebLogic,Jboss和Webshpere採用了其餘方式實現內存複製。每臺服務器任意選擇一臺服務器備份其內存中的會話信息。如圖10所示。
在這種方式中,每臺服務器都有一臺本身的對等服務器,而不是其餘全部的服務器,這種方式消除在集羣中加入過多服務器實例的話影響伸縮性的問題。
圖 10 對等服務器複製
儘管這種方式實現失效轉移有很高的性能和伸縮性,但它仍有一些限制:
爲了克服上面的4點問題,不一樣的軟件供應商採用了不一樣的方法,WebLogic採用的複製對不是對每臺服務器,而是對每一個會話。當一臺服務器實例失效後,會話數據己經分散備份到多個備份服務器上,使失效的負載均勻地分佈。
4.2.5 IBM的方式:中央狀態服務器
Websphere採用不一樣的方式實現內存複製:備份會話信息到中央的狀態服務器,如圖11所示:
圖 11 中央狀態服務器複製
這與數據庫的解決方案很是相似,不一樣之處在於專用的「會話備份服務器」代替了數據庫服務器,這種方式結合了數據庫和內存複製兩種方式的優勢。
然而,因爲有恢復失效服務器會話數據的這麼一個階段,所以其性能確定不如兩臺服務器直接複製解決方案,另外,多出來一臺備份服務器也增長了管理的複雜性。也可能因爲單臺備份服務器形成性能瓶頸。
4.2.6 Sun的方式:特殊數據庫
圖 12 特殊數據庫複製
Sun JES應用服務器採用了別的方式實現會話失效轉移,如圖12所示,它看上去很像數據庫的方式,由於它採用關係數據庫存儲會話並經過JDBC訪問全部會話數據。可是JES內部所使用的關係數據庫稱爲HADB,已經爲訪問會話作了特別優化,而且將幾乎全部的會話數據存在內存中。這樣,你能夠說它更像中央狀態服務器的方式。
4.2.7 性能因素
考慮以下問題:一臺WEB服務器中可能運行着許多WEB應用,它們中每個均可能被成百的併發用戶訪問,而每一個用戶都會產生瀏覽器會話用於訪問特定的應用。全部會話信息都將備份以便服務器失效後能轉移到其餘服務器實例中。更糟的是,會話會因爲一次次的發生如下狀況而變化,包括建立、失效、增長屬性、刪除屬性、修改屬性值。甚至是什麼都沒變,但因爲有新的訪問而使最後訪問時間變了(由此判斷何時失效會話)。所以,性能在會話失效轉移的解決方案中是個很大的因素。供應商一般會提供一些可調的參數改變服務器行爲,使之適應性能需求。
4.2.7.1 備份時機
當客戶端的請求被處理後,會話隨時改變。因爲性能因素,實時備份會話是不明智的。選擇備份頻率須要平衡。若是備份動做發生得太頻繁,性能將急劇降低。若是兩次備份的間隔太長,那麼在這間隔之間服務器失效後,不少會話信息將會丟失。無論全部的方式,包括數據庫和內存複製,下面是決定備份頻率的經常使用的選項。
在WEB請求處理結束後,發生響應以前保存數據。這種方式可以保證在失效後備份的會話數據是最新的。
會話在後臺按固定的時間間隔保存。這種方式不能保證備份的會話數據是最新的。但因爲不需在每次請求以後備份數據,於是有更好的性能。
4.2.7.2 備份粒度
當備份會話的時候,你還須要決定多少會話狀態須要保存。如下是不一樣產品全部採用的經常使用的策略。
每次都將保存全部會話。這種方式爲正確保存分佈式WEB應用的會話提供了最好保證。這種方式簡單,內存複製和數據庫存儲方式都默認採用這種方式。
當會話被修改事後,則備份整個會話。當「session.setAttribute()」或 「session.removeAttribute()」被調用後,則認爲會話被修改過。必須保證這些方法調用才修改會話屬性,這並非J2EE規範後要求的。但倒是正確實現這種方法所須要的。備份修改過的會話減小了會話存儲的數量,那些僅僅是讀取會話屬性的請求將不會觸發會話備份的動做。這將帶來比備份整個會話更好的性能。
僅僅是保存被修改過的會話屬性而不是整個會話。這將最小化備份的會話數據。這種方式帶來最好的性能及最小的網絡通訊。爲了保證這種方式工做的正確性,必須依據如下的要點。首先,只能經過調用「setAttribute()」方法修改會話狀態,而且會話數據要被序列化和備份。其次,確保屬性之間沒有交叉引用。每一個惟一的屬性鍵值下的對象圖應當被獨立地序列化和保存。若是兩個獨立的鍵值下的對象存在交叉引用,它們將不可以正確的序列化和反序列化。例如圖13所示,在一個內存複製的集羣中,會話中存有「student」和「school」對象,同時「school」對象含有到「student」對象的引用,某一個時候「school」被修改後備份到備份服務器中。在序列化和反序列化以後,在備份服務器的被還原的「school」對象的版本將包含整個對象圖,包括其引用的「student」對象。可是「student」對象能夠被獨立的修改,當它被修改後,僅僅只有它本身被備份。在序列化和反序列化以後,在備份服務器中還原「student」對象,但在此時,它將丟失與「school」對象的鏈接。儘管這種方式有最好的性能,但上述限制將影響WEB應用程序的架構和設計。特別是須要用會話保存緩存的複雜的用戶數據。
圖 13 會話複製中的交叉引用
4.2.8 其餘失效轉移的實現
如前面章節所提到的,當會話備份時粒度對於性能很是重要。然而,當前的一些實現,無論是數據庫存儲仍是內存複製,都將使用Java對象序列化技術來傳輸Java對象。這就打了個大印子,影響系統的性能,並給WEB應用程序的架構和設計帶來不少的限制。一些J2EE供應商便尋找一些特別的手段來更爲輕量地,小印子地實現WEB集羣,提供細粒度的分佈式對象共享機制用於提升集羣的性能。
4.2.8.1 Jrun的Jini技術
Jrun4將它的集羣解決方案構在Jini技術之上。Jini爲分佈式計算而生,它容許在一個單一的分佈式計算空間內建立「聯合」的設備或組件。 Jini提供查找,註冊,租用等分佈式系統服務,這對集羣環境很是有用。另外一種稱爲JavaSpace的技術構建於Jini之上,它提供了一些用於實現集羣很是有價值的特性,如對象處理,共享,遷移等。要了解有關Jini和JavaSpace更多的信息,請參考http://java.sun.com/products/jini/2_0index.html
4.2.8.2 Tangosol的分佈式緩存
Tangosol Coherence提供了一個分佈式數據管理平臺,它能夠嵌入到大多數流行的J2EE服務器中用於實現集羣環境。Tangosol Coherence同時也是提供了分佈式緩存系統用於在不一樣的JVM之間有效地共享Java對象。要了解有關Tangosol更多的信息,請參考http://www.tangosol.com
5 JNDI集羣實現
J2EE規範要求全部的J2EE容器必須提供JNDI規範的實現。JNDI在J2EE應用程序中的主要角色是用來提供一個間接層,這樣資源能夠很容易被找到,而不用關心細節。這使得J2EE組件更加可複用。
擁用全特性的集羣的JNDI對於J2EE集羣是很是重要的。全部的EJB調用都開始於在JNDI樹上查找它的Home接口,J2EE供應商根據他們的集羣結構採用不一樣的方式實現JNDI集羣。
5.1 共享全局JNDI樹
WebLogic和Jboss都有一個全局的,共享的,集羣範圍的JNDI Context供客戶端查找和綁定對象,綁定的全局JNDI Context中對象將經過IP廣播的方式在集羣中複製,這樣當一臺服務器實例停機後,被綁定的對象仍然可供查找。
圖 14 共享的全局JNDI
如圖14所示,共享的全局JNDI樹實際包括了全部本地JNDI結點上綁定的對象。集羣上每一個結點都擁有本身的名稱服務器,它們與集羣中其餘服務器相互複製全部的東西。這樣每一個名稱服務器上都擁有其餘名稱服務器對象樹的拷貝。這種冗餘結構使得全局JNDI樹高可用。
實際上,集羣的JNDI樹能夠被用作兩個目的。能夠被管理員用來部署對象和服務。在一臺服務中部署完EJB模塊或JDBC&JMS服務後,JNDI樹上的全部對象都將複製到其餘服務器實例中。在運行期,程序能夠JNDI API訪問JNDI樹存儲或者獲取對象,這些對象也將被全局複製。
5.2 獨立JNDI
Jboss和WebLogic都採用了共享全局JNDI樹的方式,Sun JES和IBM WebSphere等採用了每一個服務器都擁有獨立的JNDI樹的方式。在使用獨立JNDI樹的集羣中,成員服務器沒必要知道或關心集羣中其餘服務器。這是否意味着不想把JNDI集羣呢?全部EJB訪問都開始於在JNDI樹上查找它們的Home接口,若是沒有可集羣的JNDI樹,集羣就根本無用。
實際上,若是J2EE應用程序是類似的,獨立的JNDI樹仍然能夠得到高可用性。當集羣中全部實例都有相同的設置以及都部署相同的應用程序集,咱們說集羣是「類似的」,這種狀況下,一種被稱爲代理的特殊管理工具能夠幫助咱們獲取高可用性,如圖15所示:
圖 15 獨立JNDI
無論是Sun JES仍是WebSphere都在集羣的實例上安裝告終點代理,當部署EJB模塊或綁定其餘JNDI服務,管理控制員能夠向全部的代理髮出命令,以此實現與共享全局JNDI相同的效果。
可是獨立JNDI的方案不能支持複製在運行期綁定或獲取的對象。有如下幾個緣由:JNDI在J2EE應用程序中的主要角色是用來提供管理外部資源的間接層,並非用來作數據存儲。若是有這樣的需求,能夠採用具備HA(高可用性)特性的獨立的LDAP服務器或數據庫。Sun和IBM本身都有這樣擁有集羣特性的獨立的LDAP服務器產品。
5.3 中心JNDI
少數J2EE產品使用了中心JNDI方案,這種方案中名稱服務器駐留在單一服務器中,全部的服務器實例都註冊它們相同的EJB組件和管理對象到單一的服務器中。
名稱服務器實現了高可用性,這對客戶端是透明的。全部的客戶端都在這臺服務器中查找EJB組件,可是這種結構意味着複雜的安裝和管理,慢慢被多數供應商拋棄。
5.4 初始化訪問JNDI服務器
固然客戶端要訪問JNDI服務器的時候,它們須要知道遠程JNDI服務器的主機名/IP地址和端口,在全局或是獨立JNDI樹的方案中,有多個JNDI服務器。客戶端第一次訪問時應該鏈接哪一個呢?如何得到負載均衡和失效轉移呢?
從技術上說,一個軟件或硬件負載均衡器能夠設在遠程客戶端和全部的JNDI服務器之間執行負載均和失效轉移。可是,不多有供應商實現這種方式,這裏有些簡單的方案:
6 EJB集羣實現
EJB是J2EE技術中重要的部分,而且EJB集羣是實現J2EE集羣最大的挑戰。
EJB技術是爲分佈式計算而生。它們能夠在獨立的服務器中運行。Web服務器組件或富客戶端能夠從其餘的機器經過標準協議(RMI/IIOP)來訪問EJB。你能夠象調用你本地Java對象的方法同樣調用遠程EJB的方法。實際上,RMI/IIOP徹底掩蓋了你正在調用的對象是本地的仍是遠程的,這被稱做本地/遠程透明性。
圖 16 EJB調用機制
上圖顯示了遠程EJB的調用機制。當客戶端想使用EJB,它不能直接調用,相反,客戶端只能調用一個被稱爲「stub」的本地對象,它扮演了到遠程對象代理的角色,而且有遠程對象相同的接口。這個對象負責接受本地方法調用,而且這些調用經過網絡代理到遠程EJB。這些對象在客戶JVM中運行,而且知道如何經過RMI/IIOP跨過網絡查找真正的對象。要了解有關EJB更多的信息,請參考http://java.sun.com/products/ejb/
爲解釋EJB集羣的實現,咱們先看看在J2EE代碼中如何使用EJB的。爲了調用EJB,咱們須要
負載均衡和失效轉移能夠在JNDI查找時發生,這咱們已在上面闡述了。在EJB stub(包括EJBHome和EJBObject)的方法調用時,供應商採用如下三種方式實現EJB負載均衡和失效轉移。
6.1 智能存根(Smart stub)
正如咱們所知,客戶端能夠經過存根對象(stub)來訪門遠程的EJB,這個對象能夠經過JNDI樹獲取,甚至客戶端可能透明地從WEB服務器上下載存根類文件。
這樣存根就能夠在運行期動態地用程序生成,而其類文件也沒必要在客戶端環境的classpath或庫中。(由於它是能夠被下載的)
圖 17 智能存根
如圖17所示,BEA WebLogic和Jboss經過在存根代碼中組合幾種行爲來實現EJB集羣,而這些都是在客戶端透明運行的(客戶端不須要了解這些代碼)。這種技術叫作智能存根。
智能存根確實很聰明,它包含一組它能夠訪問的目標實例,能夠檢測這些實例的任何失效,它也包含了複雜的負載均衡和失效轉移的邏輯,用來分發請求到目標實例。並且,若是集羣拓樸改變了的話(好比新增或刪除了服務器實例),存根能夠更新它的目標實例清單來反映新的拓樸,而不須要手工從新配置。
使用智能存根實現集羣有如下優勢:
6.2 IIOP運行期庫
Sun JES Application Server使用了另外一種方法實現EJB集羣。負載均衡和失效轉移邏輯是在IIOP運行庫中實現的。好比,JES修改了「ORBSocketFactory」的實現,使它能支持集羣。如圖18所示。
圖 18 IIOP運行期
「ORBSocketFactory」的修改版有實現負載均衡和失效轉移的全部邏輯和算法,這樣就保持了存根乾淨和小。由於是在運行庫中實現的,這樣就比存根的方式更容易得到系統資源。但這種方式須要在客戶端運行一個特殊的運行庫,這樣就給與其餘J2EE產品進行互操做時帶來了麻煩。
6.3 攔截器代理
IBM Webshpere採用了定位服務精靈(Location Service Daemon-LSD),它對EJB客戶端扮演了攔截器代理的角色,如圖19所示。
圖 19 攔截器代理
在這種方式中,客戶端經過JNDI查找獲取存根,這個存根將信息路由到LSD,而不是運行了EJB的應用程序服務器。這樣LSD接收到全部進來的請求,根據負載均衡和失效轉移的策略來判斷應該將它發送到哪一個服務器實例。這種方式增長的安裝和維護集羣的額外的管理工做。
6.4 EJB的集羣支持
要調用EJB的方法,要涉及到兩種存根對象,一是EJBHome接口,另外一個是EJBObject接口。這意味着EJB潛在的須要在兩層上實如今負載均衡和失效轉移。
6.4.1 EJBHome存根支持集羣
EJBHome接口用於在EJB容器中建立和查找EJB實例,而EJBHome存根是EJBHome接口的客戶端代理。EJBHome接口不需維持任何客戶端的狀態信息。這樣,來自不一樣EJB容器中的相同EJBHome接口對於客戶端來講是一致的。當客戶端執行create()或find()調用的時候,home存根根據負載均衡和失效轉移算法從多個相同的服務器實例中選擇一個,並將調用發送到該服務器的home接口上。
6.4.2 EJBObject存根支持集羣
當EJBHome接口建立一個EJB實例,它將返回EJBObject的存根給客戶端,並讓用戶調用EJB的業務方法。集羣中已有一組可用的服務器實例,並在上面的部署了bean,可是不能將EJBObject存根對象向EJBObject接口發出調用轉發到任意一個服務器實例上,這取決於EJB的類型。
無狀態會話Bean是最簡單的狀況,由於不涉及到狀態,全部的實例均可以認爲是同樣的,這樣對EJBObject的方法調用可負載均衡和失效轉移到任意一個服務器實例上。
集羣的有狀態會話Bean與無狀態會話Bean有一點不一樣,正如咱們所知,有狀態會話Bean對於客戶端連續的請求會持有狀態信息。從技術上來講,集羣有狀態會話Bean與集羣HTTP Session是同樣的。在常規時間,EJBObject存根將不會把請求負載均衡到不一樣的服務器實例。相反,它將膠粘到最初建立的服務器實例上,咱們稱這個實例爲「主實例」。在執行過程當中,會話狀態會從主實例備份到其餘服務上去。若是主實例失效了,備份服務器將接管它。
實體Bean本質上是無狀態的,儘管它能夠處理有狀態的請求。全部的信息都將經過實體Bean自己的機制備份到數據庫中。這樣看起來實體Bean能夠象無狀態會話Bean同樣很容易獲取負載均衡和失效轉移。但實際,實體Bean多數狀況下是不負載均衡和失效轉移的。根據設計模式的建議,實體Bean 被會話外觀包裝。這樣,多數對實體Bean的訪問都是進程內會話Bean經過本地接口完成的,而不是遠程客戶端。這使得負載均衡和失效轉移沒有意義。
JMS和數據庫鏈接的集羣支持
除JSP,Servlet,JNDI和EJB以外,在J2EE中還有其餘的分佈式對象。這些對象在集羣的實現中可能支持,可能不支持。
當前,一些數據庫產品,如Oracle RAC,支持集羣環境並能夠部署到多複製,同步的數據庫實例中。然而,JDBC是一個高度狀態化的協議而且它的事務狀態直接與客戶端和服務器的 Socket鏈接綁定,因此很難獲取集羣能力。若是一個JDBC鏈接失效了,與該失效鏈接相關的全部JDBC對象也就失效了。客戶端代碼須要進行重連的動做。BEA WebLogic使用JDBC多池(multipool)技術來簡化這種重連過程。
JMS被多數J2EE服務器所支持,但支持得並不徹底,負載均衡和失效轉移僅僅被JMS代理所實現,不多有產品在JMS Destination中的消息有失效轉移的功能。
8 J2EE集羣的神話
8.1 失效轉移能夠徹底避免錯誤——否認
在Jboss的文檔中,整個章節都在警告你「你真的須要HTTP會話的複製嗎?」。是的,有時沒有失效轉移的高可用性的解決方案也是可接受而且是廉價的。失效轉移並非你想象的那麼強壯。
那麼失效轉移到底給你帶來了什麼?你可能想失效轉移能夠避免錯誤。你看,沒有會話的失效轉移,當一個服務器實例失效後,會話數據將丟失而致使錯誤。經過失效轉移,會話能夠從備份中恢復,而請求能夠被其餘服務器實例所處理,用戶根本意識不到失效。這是事實,但這是有條件的!
回想同樣咱們定義的「失效轉移」,咱們定義了一個條件是失效轉移是在「兩個方法調用之間」發生的。這是說若是你有兩個連續的對遠程對象的方法調用,失效轉移只會在第一調用成功後而且第二調用的請求發出前才能發生。
這樣,當遠程服務器在處理請求的過程當中失效了會發生什麼呢?答案是:多數狀況處理將會中止而客戶端將會看到錯誤信息。除非這個方法是等冪的(Idempotent),只有這個方法是等冪的,一些負載均衡器更智能,它會重試這些方法並將它失效轉移到其餘實例上。
爲何「等冪」重要呢,由於客戶端不知道當失效發生的時候請求被執行到什麼地方。是纔剛剛初始化仍是差很少就要完成了?客戶端無法判斷!若是方法不是等冪的,在相同方法上的兩次調用可能會兩次修改系統的狀態,而使得系統出現不一致的情形。
你可能想全部在事務中的方法都是等冪的,畢竟,若是錯誤發生,事務將被回滾,事務狀態的改變都將被複位。但事實上事務的邊界可能不包括全部的遠程方法調用過程。若是事務已經在服務器上提交了而返回給客戶端時網絡崩潰怎麼辦呢?客戶端不知道服務器的事務是不是成功了。
在一些應用程序中,將全部的方法都作成等冪的是不可能的。這樣,你只能經過失效轉移減小錯誤,而不是避免它們。拿在線商店爲例,假設每臺服務器能夠同時處理100個在線用戶的請求,當一臺服務器失效了,沒有失效轉移的解決方案將丟失100個用戶的會話數據並激怒這些用戶。而有失效轉移的解決方案中,當失效發生的時候有20個用戶正在處理請求,這樣20個用戶將被失效激怒。而其餘80個用戶正處於思考時間或在兩個方法調用之間,這些用戶能夠透明地得到失效轉移。這樣,你就需作如下的考慮:
8.2 獨立應用能夠透明的遷移到集羣結構中——否認
儘管一些供應商宣稱他們的J2EE產品有這樣的靈活性。不要相信他們!事實你要在開始系統設計時就要準備集羣,而這將影響開發和測試的全部階段。
8.2.1 Http Session
在集羣環境中,如我前面提到的,使用HTTP Session有不少限制,這取決於你的應用程序服務器採用了那種會話失效轉移的機制。第一個重要的限制就是全部保存的HTTP Session中的對象必須是可序列化的,這將限制設計和應用程序結構。一些設計模式和MVC框架會用HTTP Session保存一些不序列化的對象(如ServletContext,EJB本地接口和WEB服務引用),這樣的設計不能在集羣中工做。第二,對象的序列的反序列化對性能的影響很大,特別是數據庫保存的方式。在這樣的環境中,應該避免在會話中保存大的或是衆多的對象。若是你採用了內存複製的方式,如前所述你必須當心在會話中屬性的交叉引用。其餘在集羣環境中的主要區別是在會話無論任何屬性修改,你必須調用「setAttribute()」方法。這個方法調用在獨立的系統中是可選的。這個方法的目的是區別已修改的屬性和那些沒用到屬性,這樣系統能夠只爲失效轉移備份必要的數據,從而提升性能。
8.2.2 緩存
我經歷過的大多數J2EE項目都用了緩存來提升性能,同時流行的應用程序服務器也都提供了不一樣程度的緩存用來加快應用程序的速度。但這些緩存都是爲那些典型的獨立環境設計的,只能在單JVM實例中工做。咱們須要緩存是由於一些對象很「重」,建立它需花費大量的時間和資源。所以咱們維護了對象池用於重用這些對象,而不須要在後面建立。咱們只有當維護緩存比建立對象更廉價時才能得到性能的提升。在集羣環境,每一個JVM實例都要維護一份緩存的拷貝,這些拷貝必須同步以維持全部服務器實例狀態的一致性。有時這種類型的同步會比沒有緩存帶來更糟的性能。
8.2.3 Static變量
當咱們設計J2EE應用程序時,在架構上常常會使用一些設計模式。這些如「Singleton」的設計模式會用到靜態變量來在多對象之間共享狀態。這種方式在單服務中工做得很好,但在集羣環境將失效。集羣中的每一個實例都會在它的JVM實例中維護一份靜態變量的拷貝,這樣就破壞了模式的機制。一個使用靜態變量的例子就是用它來保持在線用戶數。用靜態變量來保存在線用戶數是一個很簡單的辦法,當用戶進入或離開時就增長和減小它。這種方式在單服務器中絕對是好的,但在集羣環境將失效。在集羣中更好的方式是將全部狀態保存到數據庫。
8.2.4 外部資源
儘管J2EE規範不支持,但爲各類目的仍然會用外部I/O的操做。例如,一些應用會使用文件系統來保存用戶上傳的文件,或是建立一個動態配置的 XML文件。在集羣環境是沒有辦法來在其餘實例之間來複制這些文件的。爲了在集羣中工做,辦法是用數據庫做爲外部文件的存放點,另外也可使用SAN(存儲區域網,Storage Area Network)做爲存放點。
8.2.5 特殊服務
一些特殊的服務只在獨立的環境中才有意義,定時服務就一個很好例子,這種服務能夠在一個固定的間隔時間有規律的觸發。定時服務經常使用於執行一些自動化管理任務。如日誌文件滾動,系統數據備份,數據庫一致性檢查和冗餘數據清理等。一些基於事件的服務也很難被遷移到集羣環境中。初始化服務就是個好例子,它只在整個系統啓動時才發生。郵件通知服務也同樣,它在一些警告條件下觸發。
這些服務是被事件而不是被請求觸發的,並且只被執行一次。這些服務使得負載均衡和失效轉移在集羣中沒多少意義。
一些產品準備了這些服務,如Jboss使用了「集羣單例設施」來協調全部實例,保證執行這些服務一次且僅有一次。基於你所選擇的平臺,一些特殊的服務可能會是把你的應用遷移到集羣結構中的障礙。
8.3 分佈式結構比並置結構更靈活——不必定
J2EE技術,尤爲是EJB,天生就是用來作分佈式計算。解耦業務功能,重用遠程組件,這些使得多層應用很是流行。可是咱們不能將全部的東西都分佈。一些J2EE架構師認爲Web層與EJB層並置得越近越好。這些計論後面會繼續。先讓我解釋一下。
圖 20 分佈式結構
如圖20所示,這是一個分佈式結構。當請求來了,負載均衡器將請求分發到不一樣服務器中的不一樣WEB容器,若是請求包含了EJB調用,WEB容器將重發EJB調用到不一樣的EJB容器。這樣,請求將被負載均衡和失效轉移兩次。
一些人看分佈式結構,他們會指出:
實際上在運行期,多數的供應商(包括Sun JES,WebLogic和Jboss)都會優化EJB調用機制,使請求首先選擇同一個服務器中的EJB容器。這樣,如圖21所示,咱們只在第一層(WEB層)作負載均衡,而後調用相同服務器上的服務。這種結構咱們稱之爲並置結構。技術上說,並置結構是分佈式結構的一種特例。
圖 21 並置結構
一個有趣的問題是,既然多數的部署在運行期都演進成了並置結構,爲何不用本地接口代替遠程接口,這將大提升性能。你固然能夠,可是記住,當你使用本地接口後,WEB組件和EJB耦合得很緊,而方法調用也是直接的而不經過RMI/IIOP。負載均衡和失效轉移分發器沒有機會介入本地接口調用。 「WEB+EJB」總體處理負載均衡和失效轉移。
但不幸的是,在集羣中使用本地接口在多數J2EE服務器中有侷限性。使用本地接口的EJB是本地對象,是不可序列化的,這一個限制就使本地引用不能保存在HTTP Session中。一些產品,如Sun JES,會將本地接口區別看待,使它們能夠序列化。這樣就能夠用在HTTP Session中。
另外一個有趣的問題是,既然並置結構這麼流行而且有好的性能,爲何還要分佈式結構呢?這在多數狀況下是有道理的,但有時分佈式結構是不可替代的。
9 結論 集羣與獨立環境不一樣,J2EE供應商採用不一樣的方法來實現集羣。若是你的項目爲作到高伸縮性而使用集羣,你應該在你的項目開始的時候就作準備。選擇符合你的需求的正確的J2EE產品。選擇正確的第三方軟件和框架並確保它們能支持集羣。最後設計正確的架構使得能從集羣中受益而不是受害。