首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

高影響力 Web 層群集(二):基於 JavaSpaces 構建

     編輯:Java 相關知識

基於 PC 商用服務器和網絡連接硬件產品可與開源 Java 軟件結合,實現 Web 服務和應用程序部署的經濟性擴展。本系列第二篇,Sing Li 深入討論典型群集系統設計方案,說明為什麼無一種適合所有情況解決方案,而基於 JavaSpaces 和 Jini 技術解決方案能自適應部署滿足不同需求。

J2EE 架構中應用服務器駐留的 Web 層,應用程序狀態信息通常以服務器端會話形式保存。通過將會話外部化、拷貝到一組聯網服務器,可創建可伸縮高度可用群集,執行 Java Web 應用和 Web 服務。第一篇《利用 JavaGroups 擴展 Web 服務及應用》分析如何用 JavaGroups 通信工具包實現內存會話復制。

本文採用略微不同方式跨越聯網服務器群集擴展 Web 應用和 Web 服務。使用技術是 JavaSpaces,作為 Jini 技術家族一部分發布的軟件服務。JavaSpaces 使可採用更高級別方法設計常規分布式系統(特別是群集系統),減少設計複雜性並增強自適應能力。通過用 JavaSpaces 實現分布式共享內存模型,可將設計重點放到服務器群集中共享會話數據上(而非第一篇中的復制)。

設計有三基本操作的分布式系統

概念上,JavaSpaces 是分布式袋子,可放入稱為實體的 Java 對象。一個空間可同時被多用戶(客戶)共享。用戶通常是網絡上其他節點或獨立 JVM。將 Java 對象放到空間(寫操作)後,所有用戶可通過移出(take 操作)或保持原樣(read 操作)讀取內容。三種簡單操作(write、take、read)封裝 JavaSpaces 所有假定,使分布式、整體並行、群集計算網絡配置成為可能。圖1 說明 JavaSpaces 操作:

圖1 JavaSpaces 基本操作

圖1 中 JavaSpaces 模板匹配機制使客戶可選擇讀對象或從空間取出對象。

JavaSpaces 本質

第一篇處理一組過程:跟蹤組成員、協調、發送接收消息(如 JavaGroups)。JavaSpaces 使無需知道消息、組、成員數量設計分布式系統。事實上,只用 read、write、take 時,JavaSpaces 分布式應用可完全寫到一線程 — 無並發內容!換句話說,用 JavaSpaces 設計群集系統可在很高抽象級別工作。此高級別工作兩主要好處:

  • 保持設計簡單易理解,有助長期維護
  • 根據不同需求迅速調整,無需大代碼改變

這些好處幾乎所有軟件系統和傳統複雜分布式應用歡迎。分布式問題總體複雜性未改變,改變的是在哪裡處理此複雜性。用 JavaSpaces,幾乎所有更複雜 orthogonal 考慮推到 API 線下面 — 推給 JavaSpaces 服務特定實現 — 使設計者處理真正應用問題。基本原則:這些複雜問題現只需解決一次,由能幹專家工程師和理論家做。要進一步理解此架構,必須越過概念看 JavaSpaces 實際表現。

應用 JavaSpaces

物理上,JavaSpace 是由 Jini 聯合服務實現的 Java 接口。net.jini.space.JavaSpace 接口是 JavaSpaces 客戶訪問服務功能唯一途徑。雖 JavaSpaces 服務是遠程服務,但服務給應用提供本地 Jini 代理(作用類似下載驅動程序),使對 JavaSpaces 接口調用都成應用程序 VM 中「內部」調用。基本操作說明:

操作說明
read搜索空間中與提供模板匹配項,返回項副本。操作停止響應直到找到項或達指定超時。
take搜索匹配項,從空間刪除項並返回副本。操作停止響應直到找到項或達超時。
write將項副本放入空間。

這些操作對象稱 Jini 項(entry)。關於如何將 Java 對象作字段附加到項信息見側欄「Jini 項」。

除三基本操作,JavaSpace 接口還有三常用方法:

操作說明
notify當匹配模板項寫入空間時,通過 Jini 遠程事件通知相關註冊表。
takeIfExists只有出現匹配才取一項,否則返回 null。不停止響應(除非未完成事務鎖定項)。
readIfExists只有匹配才讀取一項,否則返回 null。不停止響應(除非未完成事務鎖定項)。

JavaSpaces 模板匹配

read、take、readIfExists、takeIfExists、notify 使用模板匹配機制確定處理空間中哪項。模板是部分或完全填充項。項類型必須反映要匹配對象類型,即匹配指定 Java 類/接口或子類。匹配時,所有含 null 字段作通配符。所有填充字段必須在序列化二進制比較級別準確匹配。對大量並行操作和潛在硬件輔助實現進行聯合匹配。

JavaSpaces 事務

JavaSpaces 操作可在事務上下文中進行:找事務管理器服務;用 TransactionManager 創建事務;調用 JavaSpaces 操作時提供事務。事務上下文中一組操作只有兩結果 — 成功(事務提交)或不成功(事務中止)。只有事務提交時其他客戶才能見操作效果。若事務中止,JavaSpaces 操作任何效果(改變空間內容的 write 和 take)回滾,其他客戶不知曾嘗試操作。多個 JavaSpaces 執行操作可結合到一事務,可包括地理分散實現。事務管理器協調參與 JavaSpaces 服務實例間分布式兩階段提交協議。事務語義極大簡化部分失敗模式處理,因概念上已消除。結合項 m 在空間 A take 和空間 B write,創建基本事務操作。若傳輸錯誤,項 m 不從空間 A 刪除。若傳輸成功,知 m 肯定在空間 B 並從空間 A 刪除。

現已牢固掌握 JavaSpaces 概念,可在我們 Web 層群集問題應用。上篇了解到可跨越分布式 servlet 容器進行內存應用程序會話復制構建可伸縮高度可用群集。用 JavaSpaces,可不費心關注會話復制細節,將注意力放到會話共享更高級語義上。

使用分布式共享內存會話共享

用 JavaSpaces 可創建模擬共享內存系統跨越網絡軟件。此概念使可跨越空間所有客戶使用共享會話信息。圖2 展示會話共享:

圖2 用分布式共享內存實現共享會話

圖2 中應用程序會話信息在分布式共享內存維護。每個應用服務器對共享會話任何改變所有服務器實例可見。所有服務器從同一(聯網共享)內存位置讀取共享會話信息。

支持容錯主機/備用機配置

現準備用 JavaSpaces 創建分布共享內存語義。初步方案中,在一事務中結合 take 和 write 創建 update 操作。實際 Java 代碼(源代碼中可找到):

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之間,備用服務器不需對共享內存讀操作。相反,可能保有過時會話信息,因進入階段2讀取共享內存前,對會話改變一無所知。此鬆散耦合配置對支持容錯會話共享群集實現足夠。實際每個群集應用可能有獨特需求。上述方案雖有效但可能不足滿足需求。用 JavaSpaces 實現群集方案使可只用最少設計代碼改變適應不同設計需求。為親身體驗此靈活性,研究如何創建更緊密耦合方案,除容錯外還支持負載平衡。

擴展為具有負載平衡群集

為充分利用資源,一些群集應用可能要求用備用服務器處理進入請求。此配置主要好處:請求處理負荷分散到群集幾服務器上(多於一備用服務器時),應用可擴展接受更大並發用戶群(稱外擴)。此配置稱負載平衡群集配置。一些實現將收到請求轉發給負載最少服務器,另一些用循環請求分配。圖4 說明概念:

圖4 負載平衡群集

負載平衡群集中容錯自動。若一服務器不能工作,所有新請求分配給剩餘服務器。

對負載平衡群集,不能用第一方案共享內存實現。因鬆散耦合方案會使會話信息過時。此方案中不能假定主服務器唯一修改特定共享會話服務器。所有本地保持會話信息任何時候都可能過時不同步。必須保證對共享會話數據讀-修改-寫操作是原子。

原子會話讀-修改-寫

為保證群集中所有服務器任何時候可處理會話請求,必須對會話數據維護嚴格限制:服務器不能使用任何本地保存共享會話鏡像作修改源 — 因鏡像可能過時破壞信息。相反,服務器必須在修改會話數據前從共享內存讀取它。

更新第一篇 JavaGroups 購物車應用用 JavaSpaces 聯網共享內存(下載地址見參考資料)。應用展示單個購物車應用程序會話內部情況。改變代碼局限在兩類中:

類名說明
JSCart以前 JGCart,主 GUI 應用。無自己維護會話數據副本,現讀取修改會話數據用 CartSessionManager 類。
CartSessionManager為 JSCart 提供共享會話功能。封裝所有 JavaSpaces 操作和處理。

JSCart 與 CartSessionManager 分工明確。JSCart 維護 GUI 並對所有需會話數據用 CartSessionManager。CartSessionManager 用 JavaSpaces 聯網共享內存實現 API — 客戶看不到。意味著 CartSessionManager 不知道數據如何包含在會話中。會話細節對其不透明。這使原子讀-修改-寫循環實現變困難,因:只有 CartSessionManager 知如何讀寫共享內存;只有 JSCart 知如何有效修改會話。代碼中借助 com.ibm.devworks.javaspace.PrepReadModifyWrite 接口實現:

public interface PrepReadModifyWrite {
    public void afterRead(Object readSession, Object token);
}

接口只有一方法 afterRead()。JSCart 需執行讀-修改-寫時,調用 CartSessionManager readModifyWrite() 傳遞實現 PrepReadModifyWrite 對象(例中即 JSCart 類本身)。用戶用 GUI 向購物車添加貨物時,由 JSCart 發出調用,在 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

  1. 在 code\jini2 目錄啟動 Jini 和 JavaSpaces 環境,鍵入 startup(遵循每目錄 README.TXT 指示設置系統)。
  2. 用 scripts 目錄 seedspc.bat 運行會話 seed 實用程序,鍵入 scripts\seedspc。
  3. 在 scripts 目錄運行 runcart.bat 執行 JSCart 兩個或更多實例,鍵入 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 接口:

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() 看到代碼:

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), 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 客戶必須在失效前更新租約。代碼中用 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 層群集方案時用更少代碼做更多事。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved