基於 PC 的商用服務器和網絡連接硬件產品可以與開放源代碼 Java 軟件相結合,以實現 對 Web 服務和應用程序部署的經濟性擴展。在本 高影響力的Web 層群集系列文章的第二篇 中,Sing Li 深入討論了典型的群集系統設計方案,並說明了為什麼沒有一種適合所有情況 的解決方案,而基於JavaSpaces 和 Jini 技術的解決方案能夠自適應地部署以滿足不同的要 求。
在 J2EE 體系結構中應用服務器駐留的 Web 層,應用程序狀態信息通常是以服務器端會 話的形式保存的。通過將這些會話外部化、並將它們拷貝到一組聯網的服務器中,就可以創 建一個可伸縮和高度可用的群集,用以執行 Java Web 應用程序和 Web 服務。在本系統的第 一篇文章“ 利用JavaGroups擴展Web服務及應用”中,我們分析了如何用 JavaGroups 通信工具包實現內存中的會話復制。
在這第二篇文章中,我們將采用稍微不同的方式跨越聯網的服務器群集擴展 Web 應用程 序和 Web 服務。我們要使用的技術是 JavaSpaces,這是一個作為 Jini 技術家族的一部分 發布的軟件服務(參閱 參考資源)。JavaSpaces 使您可以采用更高級別的方法設計常規的分 布式系統(特別是群集的系統),減少設計的復雜性並增強自適應能力。通過用 JavaSpaces 實現分布式的共享內存模型,使我們可以將設計重點放到服務器群集中共享會話數據上(而不 是像在第一篇文章中那樣放在復制上)。
設計有三個基本操作的分布式系統
在概念上,JavaSpaces 是分布式的袋子,在其中我們可以放入稱為 實體的 Java 對象( 參閱側欄“ Jini 實體”)。一個空間可以同時被多個用戶(客戶)共享。用戶通常 是網絡上其他節點或者獨立的 Java 虛擬機(Java virtual machines JVM)。將一個 Java 對 象放到空間(帶寫操作)後,這個空間的所有用戶都可以通過將它從空間中移出(用 take 操作 )或者讓它保持原樣(用 read 操作)而讀取其內容。
這三種簡單的操作(write、 take 和 read)很好地封裝了 JavaSpaces 的所有假定,並使 得分布式的、整體並行的、或者群集的計算網絡配置成為可能。圖 1 說明了用 JavaSpaces 進行的操作:
圖 1. JavaSpaces 的基本操作
在圖1中,JavaSpaces 的模板匹配機制(參閱側欄“ JavaSpaces 中的模板匹配 ”)使客戶可以選擇讀對象或者從空間中取出對象。
JavaSpaces的本質
本系列的第一篇文章處理的是一組過程:跟蹤組成員、以及協調、發送或者接收消息(就 像在 JavaGroups 中一樣)。JavaSpaces 使我們可以無需知道消息、組或者成員數量就可以 設計分布式系統。事實上,在只使用 read、write 和 take 操作時,JavaSpaces 分布式應 用程序可以完全編寫到一個線程中 -- 沒有並發的內容!換句話說,在用 JavaSpaces 設計 群集系統時,可以在一個很高的抽象級別上工作。在這種高級別上工作的兩個主要好處是可 以:
Jini項
一個 Jini 項是一個實現了 net.core.jini.Entry 接口的 Java 對象。這個接口是一個 marker 接口,它繼承了 java.io.Serializable ,沒有需要實現的方法。每一個 Jini 項都 可以包含 Java 對象字段。這些字段也必須是序列化的。在使用 JavaSpaces 時,項的每一 個字段都是獨立序列化的。項不是序列化的對象圖的根,這意味著如果兩個字段引用了同一 個對象,那麼會分別序列化被引用的同一個對象的兩個副本。
保持設計非常簡單和容易理解,有助於長期的維護
根據不同的要求迅速調整,不需做大的代碼改變
這些好處是幾乎所有軟件系統以及傳統的復雜分布式應用程序都歡迎的。當然,分布式問 題的總體復雜性並沒有改變。改變 的是在哪裡對這種復雜性進行處理。使用 JavaSpaces, 幾乎所有更復雜的 orthogonal 考慮都推到 API 線下面了 -- 推給了 JavaSpaces 服務的特 定實現 -- 使設計者可以處理真正的應用問題。基本原則是這些復雜問題現在只需解決一次 ,可以由能干的專家工程師和理論家去做。要進一步理解這種體系結構,我們必須越過概念 看一下 JavaSpaces 的實際表現。
應用 JavaSpaces
物理上講,一個 JavaSpace 就是一個由 Jini 聯合的服務(參閱側欄“ 關於 Jini 的種種”)所實現的 Java 接口。這個 net.jini.space.JavaSpace 接口是 JavaSpaces 客戶訪問服務功能的唯一途徑。雖然 JavaSpaces 服務是一種遠程服務,但是服務給應用程 序提供了本地 Jini 代理(其作用類似於下載的驅動程序),使對 JavaSpaces 接口的調用都 成為應用程序 VM 中“內部”的。表 1 給出了每一種操作的更詳細說明。
表 1. 基本 JavaSpaces 操作
操作 說明 read 在空間中搜索與所提供的模板相匹配的項,並返回該項的副本。這種操作 會停止響應直到找到一項或者達到了指定的超時時間。 take 在空間中搜索與所提供的模板相匹配的項,從空間中刪除該項並返回該項 的一個副本。這種操作會停止響應直到找到一項或者達到了指定的超時時間。 write 將項的副本放入空間中。
這些基本的操作所操作的對象稱為 Jini 項(entry) 。關於如何將 Java 對象作為字段 附加到這些項上的信息參閱側欄“ Jini 項”。
除了這三種基本的操作,JavaSpace 接口還有三個常用的方法,如表 2 所示:
表 2. 其他 JavaSpaces 操作
操作 說明 notify 當與所提供的模板相匹配的項寫入空間時,通過 Jini 遠程事件通知相關 的注冊表。 takeIfExists 只有當出現匹配時才取一項,否則返回 null。這種操作在查找匹配的項時 不會停止響應(除非未完成的事務鎖定了這個項 -- 參閱有關事務的下一節)。 readIfExists 只有當匹配時才讀取一項,否則返回 null。這種操作在查找匹配的項時不 會停止響應(除非未完成的事務鎖定了這個項 -- 參閱有關事務的下一節)。
JavaSpaces中的模板匹配
read、 take 、 readIfExists 、 takeIfExists 和 notify 方法都使用 JavaSpaces 的 模板匹配機制確定處理 JavaSpaces 中的哪一項。更明確地說,模板就是一個部分或者完全 填充的項。項的類型必須反映要匹配的對象的類型,這意味著必須匹配指定的 Java 類/接口 或者子類。在匹配時,所有包含 null 的字段都作為通配符。所有填充的字段都必須在序列 化的二進制比較級別上准確匹配。對大量並行的操作和潛在的硬件輔助實現進行這種聯合匹 配。有關 javaSpaces 模板匹配的更多信息參閱 參考資料中的 JSK 文檔。
JavaSpaces中的事務
JavaSpaces 操作可以在事務的上下文中進行。這是由以下步驟實現的:
找出事務管理器服務
用 TransactionManager 接口創建一個事務
在調用 JavaSpaces 操作時提供事務
在事務的上下文中執行的一組操作只能有兩種結果 -- 成功(事務被提交)或者 不成功(事 務中止)。只有在事務提交時其他 JavaSpaces 客戶才能看到一組操作的效果。如果事務中止 了,那麼 JavaSpaces 操作的任何效果(即改變空間內容的 write 和 take)都會回滾並且其 他客戶將不會知道曾經試圖進行過這些操作。
在多個 JavaSpaces 中執行的操作可以結合到一個事務中,這些 JavaSpaces 可以包括在 地理上分散的實現。在這個外殼下,事務管理器協調參與 JavaSpaces 服務實例之間的分布 式兩階段提交協議。
事務語義極大地簡化了對部分失敗模式的處理,因為從概念上已經將它消除了。通過結合 項 m 在空間 A 中的 take 和空間 B 中的 write,我們就創建了一個最基本的事務操作。如 果傳輸時有任何錯誤,那麼項 m 不會從空間 A 中刪除。如果傳輸成功了,那麼我們知道 m 肯定是在空間 B 中並從已空間 A 中刪除了。
現在我們已經牢固掌握了 JavsSpaces 的概念,就可以在我們的 Web 層群集問題上應用 它們了。在上一篇文章中,我們了解到可以跨越一組分布式 servlet 容器進行內存中應用程 序會話復以制構建可伸縮的、高度可用的群集。使用 JavaSpaces,我們就可以不用費心關注 如何處理實際的會話復制細節。相反,我們將注意力放到會話共享的更高級語義上。
使用分布式共享內存進行會話共享
使用 JavaSpaces,我們就可以創建一個模擬共享內存系統的跨越網絡的軟件。這個概念 使我們可以跨越空間中的所有客戶使用共享會話信息。圖 2 展示了會話共享:
圖 2. 用分布式共享內存實現共享的會話
在圖 2 中,應用程序會話信息是在分布式共享內存中維護的。每一個應用服務器對共享 的會話的任何改變都可以被所有服務器實例看見。所有服務器從同一個(聯網共享的)內存位 置讀取共享的會話信息。
支持容錯的主機/備用機配置
我們現在准備用 JavaSpaces 創建一個分布共享內存語義。在這個初步方案中,我們在一 個事務中結合了一個 take 操作和一個 write 操作以創建一個 update 操作。清單 1 是實 際的 Java 代碼,可以在提供的源代碼中找到它:
清單 1. updateSession() 方法
public boolean updateSession(Serializable sess) {
if ((spaceService == null) || (transactionService == null))
return false;
Transaction transact = null;
try {
Transaction.Created tc =
TransactionFactory.create(transactionService,
TRANSACTION_LEASE);
transact = tc.transaction;
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
try {
SessionEntry tmpEntry = (SessionEntry) spaceService.take(
new SessionEntry(SESSION_KEY, null),
transact, JavaSpace.NO_WAIT);
if (tmpEntry == null) {
transact.abort();
return false;
}
spaceService.write(new SessionEntry(SESSION_KEY, sess),
transact, LONG_LEASE);
transact.commit();
} catch (Exception ex) {
ex.printStackTrace();
try {
transact.abort(); }
catch (Exception ex2) {
ex2.printStackTrace();
}
return false;
}
return true;
}
在這段代碼中,事務事件 take 和 write 組合成為原子(參閱側欄“ 我們為什麼使 用事務?”中的解釋)。注意 write 操作不對從空間中取出的數據進行操作。相反,它 寫入從 JSCart 應用程序傳遞過來的一個序列化對象的鏡像(這是一個由應用程序維護的會話 鏡像)。
這種設計對於通過主機/備用機配置使用容錯功能的系統很有用。在這種配置下,一個主 服務器接收所有進入的請求,一個備用服務器只有在主服務器崩潰或者出現故障時才會啟用 。圖 3 展示了這種方案:
圖 3. 容錯配置
我們為什麼使用事務?
因為通過 take 操作取得的所有項對於其他用戶來說都不再可用,所以初看起來我們不需 要對更新時進行的 take 和 write 操作使用事務。不過,事務的主要目的並不是防止對項的 並發訪問,而是防止當服務器在進行了 take 後但是在 write 之前崩潰使得會話消失。使用 事務保證超過事務租約到期後,會話項可以恢復到事務前的值。
因為它一般不處理任何收到的請求,所以圖 3 中的備用服務器只在兩種情況下訪問共享 會話信息:
在啟動時,以獲得當時的會話狀態
當主服務器崩潰時,以獲得最新的會話狀態
不過,在階段 1 和階段 2 之間,備用服務器不需要對共享內存進行任何讀操作。相反, 它可能會在階段 1 和階段 2 之間保有過時的會話信息,因為在進入階段 2 讀取共享的內存 之前,它對於會話的改變是一無所知的。
這種松散耦合的配置對於支持容錯的會話共享群集的實現是足夠的了。實際上,每一個群 集應用程序都可能有自己獨特的需求。上述解決方案雖然是有效的,但是可能不足以滿足那 些需求。用 JavaSpaces 實現群集的解決方案使我們可以只用最少的設計和代碼改變就可以 適應不同的設計需求。為了親身體驗這種靈活性,我們可以研究一下如何創建一個更緊密耦 合的解決方案,它除了容錯外還支持負載平衡。
擴展為具有負載平衡的群集
為了充分利用現有的資源,一些群集的應用程序可能要求使用(前一方案中的)備用服務器 處理進入的請求。這種配置的主要好處是請求處理負荷分散到了群集中的幾個服務器上(在有 多於一個備用服務器的情況時),因此應用程序可以擴展為接受更大的並發用戶群(稱為 外擴 )。這種配置一般稱為 負載平衡群集配置。一些實現可能將收到的請求轉發給負載最少的服 務器,而另一些實現可能使用循環請求分配方案。圖 4 說明了這個概念:
圖 4. 負載平衡群集
在負載平衡的群集中容錯是自動的。如果一個服務器不能工作,那麼所有新進入的請求都 分配給剩下的服務器。
對於負載平衡的群集,我們不能使用第一個方案中使用的共享內存實現。這是因為松散耦 合的解決方案會使會話信息過時。
在這種方案中,我們不能假定主服務器是唯一修改特定共享會話的服務器。所有本地保持 的會話信息在任何時候都可能過時並且不同步。要解決這個問題,我們必須保證對共享會話 數據的讀-修改-寫操作是一個原子。
原子會話讀-修改-寫
為了保證群集中的所有服務器可以在任何時候處理會話的請求,我們必須對會話數據的維 護加以嚴格的限制。即服務器必須不能使用任何本地保存的共享會話鏡像作為修改的源 -- 因為這個鏡像可能是過時的並會破壞信息。相反,服務器必須在修改會話數據之前從共享內 存中讀取它。
我們更新了第一篇文章中的 JavaGroups 購物車應用程序以使用 JavaSpaces 聯網共享內 存(參閱 參考資料 中的下載地址)。這個應用程序展示了單個購物車應用程序會話的內部情 況。改變的代碼局限在兩個類中,如表 3 所示:
表 3. JSCart 應用程序中主要改變的兩個類
類名 說明 JSCart 以前的 JGCart,它是主 GUI 應用程序。這個應用程序沒有自己維護會話 數據的副本,現在它在讀取或者修改會話數據時使用 CartSessionManager 類。 CartSessionManager 這個類為 JSCart 提供了共享會話功能。它封裝了所有 JavaSpaces 操作 和處理
JSCart 與 CartSessionManager 之間的分工是明確的。 JSCart 維護 GUI 並對所有需要 的會話數據使用 CartSessionManager 。 CartSessionManager 用 JavaSpaces 聯網共享內 存實現了其 API -- 客戶是看不到它的。這意味著 CartSessionManager 不知道數據是如何 包含在會話中的。會話細節對於 CartSessionManager 來說是不透明的。這使原子讀-修改- 寫循環變的實現變得有些困難了,因為:
只有 CartSessionManager 知道如何讀和寫共享內存
只有 JSCart 知道如何有效地修改會話
在代碼中,讀-修改-寫是在一個名為 com.ibm.devworks.javaspace.PrepReadModifyWrite 的接口幫助下實現的。清單 2 顯示了 這個接口的定義:
清單 2. PrepReadModifyWrite 接口
public interface PrepReadModifyWrite {
public void afterRead(Object readSession, Object token);
}
在這個接口中只有一個方法,稱為 afterRead() 。
當 JSCart 需要執行讀-修改-寫操作時,它調用 CartSessionManager 對象的 readModifyWrite() 方法,傳遞一個實現了 PrepReadModifyWrite 接口的對象(在我們的例 子裡,就是 JSCart 類本身)。
當用戶用 GUI 向購物車中添加貨物時,由 JSCart 發出這個調用。它是在 itemOrdered () 方法中處理的,如清單 3 所示:
清單 3. itemOrdered() 方法中的讀-修改-寫
public void itemOrdered(OrderEvent ev) {
System.out.println("JSCart received ordered event - " + ev);
boolean success = sessionMgr.readModifyWrite(this,
(Object) new OrderEvent(ev.getDesc(), ev.getPrice())) ;
}
這使事務中的 CartSessionManager 可以自動:
執行 JavaSpaces take 操作
用 PrepReadModifyWrite 接口的 afterRead() 方法向 JSCart 返回不透明的會話以進行 修改
用修改後的數據執行 JavaSpaces write 操作
seed 空間的需要
在 JavaSpaces 的當前規范中,沒有已知的一組 n 個客戶可靠地合作以在空間中 “seed”共享 singleton 項的一般性方法。這就是為什麼會有一個用 SessionSeeder 類實現的會話 seed 實用程序的原因。這個工具在任何客戶啟動之前將初始 會話項寫入空間。在概念上,我們可以在聯網的內存初始化以共享或者映射要共享的區域時 看到這一點。
用分布式共享內存測試 JSCart
我們可以用以下步驟試一試這個版本的 JSCart :
1.在 code\jini2 目錄中啟動 Jini 和 JavaSpaces 環境,在 code\jini2 目錄中,鍵入 startup(保證遵循每一個目錄中的所有 README.TXT 中的指示來設置系統)。
2.用 scripts 目錄中的 seedspc.bat 批處理文件運行會話 seed 實用程序。在代碼目錄中,鍵入 scripts\seedspc(參閱側欄“ seed 空間的需要”有關 seed 空間的更多信息)。
3.在 scripts 目錄中,運行 runcart.bat 批處理文件以執行 JSCart 的兩個或者更多實例,在 code 目錄中,鍵入 scripts\runcart。
如果需要從源代碼編譯為二進制文件,在 code\src 目錄中使用 compile.bat 批處理文件。
JSCart 只使用了一個共享會話以使它保持簡單和容易觀察。在生產方案中,通常都有多 個會話項,每一個會話項可以獨立修改。還會需要一個單獨的共享項以保持所有會話 ID。
現在試著在第一個購物車中添加貨物。這相當於讓添加貨物請求進入第一台服務器。注意 兩個購物車看上去不同步,類似於圖 5:
圖 5. 顯示的 JavaSpaces 購物車看上去不同步
不過,如果我們現在在第二個服務器/購物車上模擬對同一個會話的請求(通過向第二個購 物車添加產品),那麼我們將會看到第二個購物車立即“補充”了第一個購物車上 的貨物。可以在任何數量的購物車上任意多次添加貨物,模擬將收到的對同一會話的請求分 配給不同的服務器的情況,您會發現共享的會話信息一直保護一致。
雖然這種會話共享方法對於具有容錯能力的負載平衡群集服務器是足夠的,但是它仍然不 能滿足我們的需要。實際上需要在發生共享內存寫操作時通知可視化的購物車,這樣它就可 以更新其可視化 GUI 了。這種要求看來比在大多數群集系統中使用的更嚴格。解決方案也需 要更多的帶寬,因為每次共享內存寫操作現在都會在網絡上產生通知風暴。不管怎麼說, JavaSpaces 再次迎接了挑戰,可以對不同分布式應用程序的的不同需求進行調整。
使用遠程事件進行會話狀態改變通知
JSCart 需要隨時可視化地反映共享會話的狀態。為了滿足這種特殊的要求, JSCart 實 例必須在會話狀態改變時得到通知。這意味著在任何一個實例上發生共享內存寫操作時,必 須通知所有 JSCart 實例。
我們的 CartSessionManager 類專門為此提供了一個 addDistributedWriteListener() 方法。 JSCart 調用這個方法以將自己注冊為一個監聽器。 JSCart 實現了 com.ibm.devworks.javaspace.DistributedWriteListener 接口,詳見清單 4:
清單 4. 用於會話改變通知的 DistributedWriteListener 接口
public interface DistributedWriteListener extends java.util.EventListener {
public void sessionChanged(Object session);
}
CartSessionManager 保證當共享會話信息被修改時調用 sessionChanged() 方法。這使 JSCart 可以用最新的會話數據更新其 GUI。
在內部, CartSessionManager 使用 JavaSpaces notify 操作實現事件通知。 JavaSpaces 使用 Jini 的遠程事件通知機制(參閱 參考資料)以進行遠程方法調用(RMI),以 在匹配特定模板的項寫入 JavaSpaces 時回調監聽的客戶。在這之前,客戶必須用 JavaSpaces notify() 方法注冊一個遠程監聽器。可以在 CartSessionManager 類的 registerRemoteEvent() 方法中看到這段代碼,如清單 5 所示:
清單 5. 注冊 JavaSpaces 遠程通知的 registerRemoteEvent() 方法
private boolean registerRemoteEvent(Configuration config) {
if ((spaceService == null) || (transactionService == null))
return false;
EventRegistration evtReg;
leaseMgr = new LeaseRenewalManager();
try {
Exporter exp = (Exporter) config.getEntry(
CONFIG_COMP,
"serverExporter", Exporter.class,
new net.jini.jeri.BasicJeriExporter(
net.jini.jeri.tcp.TcpServerEndpoint.getInstance(0),
new net.jini.jeri.BasicILFactory(),
false, true));
evtReg =
spaceService.notify( new SessionEntry(SESSION_KEY, null),
null, (RemoteEventListener)
exp.export(this),
/* LeaseTime is 3 minutes */ 3 * 60 * 1000 ,
null);
leaseMgr.renewFor(evtReg.getLease(),
Lease.FOREVER, 3 * 60 * 1000, null);
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
return true;
}
租用和自修復的、長壽的網絡
Jini 大量使用限期租約以支持自修復的、長壽的網絡的概念。一個租約用是當以客戶的 名義要求或者占有網絡資源(像內存這樣的物理資源或者像事務這樣的概念資源)時,由資源 擁有者授予的。例如,一個由事務管理程序創建的事務是有租約的。這保證在創建了事務的 Jini 客戶崩潰並再也不加入網絡時,資源也會最終釋放。想要在租約期後繼續租用的 Jini 客戶必須在其失效前更新其租約。
注意在清單 5 中我們使用了一個 JavaSpaces LeaseRenewalManager 幫助器類以保證對 事件的租約得到更新。遠程事件注冊在 Jini 中是有租約的,因為每一個注冊會占用一定的 網上資源。通過為 LeaseRenewalManager 幫助器類指派租約更新,我們可以保證當這個 JSCart 實例運行時租約不會失效。另一個值得注意的地方是在 exporter 對象中使用 export() 方法,以便可以用 RMI 訪問我們的對象。現在 Jini 2.0 對 RMI 的增強要求顯式 導出遠程對象-- 如果我們是從 exporter 對象繼承的話,自動 stub 插入將不再發生。
圖 6 顯示了事件注冊和遠程通知序列,顯示了共享會話狀態改變通知是如何實現的:
圖 6. 事件注冊和遠程通知序列
為了試驗支持分布式通知的 JSCart,需要從 JSCart 和 CartSessionManager 類的源代碼中小心地去掉明確標記出的一段代碼注釋。用 code\src 目錄中的 rcompile.bat 文件重新編譯。這還會創建所需要的RMI stub,並生成 jscart-dl.jar 文件。更多細節參閱源代碼中的 README.TXT 文件。
結束語
使用 JavaSpaces 和 Jini 技術使我們可以在更高的級別上設計群集系統。使用三種基本 的 read 、 take 和 write 操作,我們創建了一個在群集中共享應用服務器會話的分布式共 享內存模型。JavaSpaces 對事務和租約的支持使我們可以圍繞部分故障進行設計並把注意力 放到會話共享機制上。
每一個分布式應用程序都有自己獨特的需求,創建一個一般性地滿足所有應用程序的基本 API 是很困難的 -- 如果不是不可能的話。我們分析了主機/備用機容錯會話共享群集與完全 負載平衡的會話共享群集配置所帶來的需求的不同之處。JavaSpaces 可以在更高的級別上進 行調整,使我們可以創建只需要很少的改變就可以進行調整滿足這些應用程序的解決方案。 我們的可視化購物車實際上需要分布式會話寫通知才可以正確地工作。JavaSpaces 通過其對 空間-寫的遠程通知的支持再次發揮了作用。不需要重新設計,只用很少的代碼修改就實現了 定制的解決方案。
圖 7 顯示了設計可能性的連續頻譜,從最頂部的概念性到最底部的物理性:
圖 7. 可用的群集技術從概念性到物理性的頻譜
在頻譜的頂部,解決方案設計要容易得多,因為我們可以使用與要處理的問題更接近的對 象和組件。例如,我們可以在“會話”對象的基礎上工作。在領域的底部是原始 的實現細節(如“在網絡上發送這個包”)。雖然在較低的級別上可能提供了更多 的控制和靈活性,但是工程師也需要做多得多的設計、編碼和測試工作。在更高的級別上工 作使工程師可以迅速地創建定制的解決方案並注重實際的問題,但是同時需要有對底層的實 現有更多的信賴和信任。
群集解決方案設計者可以在頻譜中任何位置選擇支持技術。我們可以看到 JavaSpaces 解 決方案處於相當高的位置,而我們上一次討論的 JavaGroups 解決方案就要低一些。在本系 列的下一篇、也是最後一篇文章中,我們將進一步分析正在出現的分布式系統組裝技術,它 比 JavaSpaces 更高,處在概念性頻譜位置。我們將討論它是如何幫助我們在設計高影響力 的 Web 層群集解決方案時用更少的代碼做更多的事情的。
本文配套源碼