|
J2EE應(yīng)用程序的Web層狀態(tài)復(fù)制 大多數(shù)具有一定重要性的 Web 應(yīng)用程序都要求維護(hù)某種會(huì)話狀態(tài),如用戶購(gòu)物車的內(nèi)容。如何在群集服務(wù)器應(yīng)用程序中治理和復(fù)制狀態(tài)對(duì)應(yīng)用程序的可伸縮性有顯著影響。許多 J2SE 和 J2EE 應(yīng)用程序?qū)顟B(tài)存儲(chǔ)在由 Servlet API 提供的 HttpSession 中。本文作者分析了狀態(tài)復(fù)制的一些選項(xiàng)以及如何最有效地使用 HttpSession 以提供好的伸縮性和性能。 不管正在構(gòu)建的是 J2EE 還是 J2SE 服務(wù)器應(yīng)用程序,都有可能以某種方式使用 Java Servlet —— 可能是直接地通過(guò)像 jsp 技術(shù)、Velocity 或者 WebMacro 這樣的表示層,也可能通過(guò)一個(gè)基于 servlet 的 Web 服務(wù)實(shí)現(xiàn),如 Axis 或者 Glue。Servlet API 提供的一個(gè)最重要的功能是會(huì)話治理 —— 通過(guò) HttpSession 接口進(jìn)行用戶狀態(tài)的認(rèn)證、失效和維護(hù)。 會(huì)話狀態(tài) 幾乎每一個(gè) Web 應(yīng)用程序都有一些會(huì)話狀態(tài),這些狀態(tài)有可能像記住您是否已登錄這么簡(jiǎn)單,也可能是您的會(huì)話的更具體的歷史,如購(gòu)物車的內(nèi)容、以前查詢結(jié)果的緩存或者 20 頁(yè)動(dòng)態(tài)問(wèn)卷表的完整響應(yīng)歷史。因?yàn)?HTTP 協(xié)議本身是無(wú)狀態(tài)的,所以需要將會(huì)話狀態(tài)存儲(chǔ)在某處并與瀏覽會(huì)話以某種方式相關(guān)聯(lián),使得下次請(qǐng)求同一 Web 應(yīng)用程序的頁(yè)面時(shí)可以輕易地獲取。幸運(yùn)的是,J2EE 提供了幾種治理會(huì)話狀態(tài)的方法 —— 狀態(tài)可以存儲(chǔ)在數(shù)據(jù)層,用 Servlet API 的 HttpSession 接口存儲(chǔ)在 Web 層,用有狀態(tài)會(huì)話 bean 存儲(chǔ)在 Enterprise JavaBeans(EJB)層,甚至用 cookie 或者隱藏表單字段將狀態(tài)存儲(chǔ)在客戶層。不幸的是,會(huì)話狀態(tài)治理不當(dāng)會(huì)帶來(lái)嚴(yán)重的性能問(wèn)題。 假如應(yīng)用程序能夠在 HttpSession 中存儲(chǔ)用戶狀態(tài),這種方法通常比其他方法更好。在客戶端用 HTTP cookie 或者隱藏表單字段存儲(chǔ)會(huì)話狀態(tài)有很大的安全風(fēng)險(xiǎn) —— 它將應(yīng)用程序的一部分內(nèi)部?jī)?nèi)容暴露給了非受信任的客戶層。(一個(gè)早期的電子商務(wù)網(wǎng)站將購(gòu)物車內(nèi)容(包括價(jià)格)存儲(chǔ)在隱藏表單字段中,從而可以很輕易被非法利用,讓任何了解 Html 和 HTTP 的用戶可以以 0.01 美元購(gòu)買(mǎi)任何商品。噢)此外,使用 cookie 或者隱藏表單字段很混亂,輕易出錯(cuò),并且脆弱(假如用戶禁止在瀏覽器中使用 cookie,那么基于 cookie 的方法就完全不能工作)。 在 J2EE 應(yīng)用程序中存儲(chǔ)服務(wù)器端狀態(tài)的其他方法是使用有狀態(tài)會(huì)話 bean,或者在數(shù)據(jù)庫(kù)中存儲(chǔ)會(huì)話狀態(tài)。雖然有狀態(tài)會(huì)話 bean 在會(huì)話狀態(tài)治理方面有更大的靈活性,但是在可能的情況下,將會(huì)話狀態(tài)存儲(chǔ)在 Web 層仍然有好處。假如業(yè)務(wù)對(duì)象是無(wú)狀態(tài)的,那么通??梢詢H僅添加更多 Web 服務(wù)器來(lái)擴(kuò)展應(yīng)用程序,而不用添加更多 Web 服務(wù)器和更多 EJB 容器, 這樣的成本一般要低一些并且輕易完成。使用 HttpSession 存儲(chǔ)會(huì)話狀態(tài)的另一個(gè)好處是 Servlet API 提供了一種會(huì)話失效時(shí)通知的輕易方法。在數(shù)據(jù)庫(kù)中存儲(chǔ)會(huì)話狀態(tài)的成本可能難以承受。 servlet 規(guī)范沒(méi)有要求 servlet 容器進(jìn)行某種類型的會(huì)話復(fù)制或者持久性,但是它建議將狀態(tài)復(fù)制作為 servlet 首要 存在理由(raison d'etre) 的重要部分,并且它對(duì)作為進(jìn)行會(huì)話復(fù)制的容器提出了一些要求。會(huì)話復(fù)制可以提供大量好處 —— 負(fù)載平衡、伸縮性、容錯(cuò)和高可用性。相應(yīng)地,大多數(shù) servlet 容器支持某種形式的 HttpSession 復(fù)制,但是復(fù)制的機(jī)制、配置和時(shí)間是由實(shí)現(xiàn)決定的。 HttpSession API 簡(jiǎn)單地說(shuō),HttpSession 接口支持幾種方法,servlet、JSP 頁(yè)或者其他表示層組件可以用這些方法來(lái)跨多個(gè) HTTP 請(qǐng)求維護(hù)會(huì)話信息。會(huì)話綁定到特定的用戶,但是在 Web 應(yīng)用程序的所有 servlet 中共享 —— 不特定于某一個(gè) servlet。一種考慮會(huì)話的有用方法是,會(huì)話像一個(gè)在會(huì)話期間存儲(chǔ)對(duì)象的 Map —— 可以用 setAttribute 按名字存儲(chǔ)會(huì)話屬性,并用 getAttribute 提取它們。HttpSession 接口還包含會(huì)話生存周期方法,如 invalidate() (它通知容器應(yīng)丟棄會(huì)話)。清單 1 顯示 HttpSession 接口最常用的元素: 清單 1. HttpSession API public interface HttpSession { boolean isNew(); 一種更常用的方式是將負(fù)載平衡與會(huì)話相似性(affinity) 結(jié)合起來(lái) —— 負(fù)載平衡器可以將會(huì)話與連接相關(guān)聯(lián),并將會(huì)話中以后的請(qǐng)求發(fā)送給同一服務(wù)器。有很多硬件和軟件負(fù)載平衡器支持這個(gè)功能,并且這意味著只有主連接主機(jī)和會(huì)話需要故障轉(zhuǎn)移到另一臺(tái)服務(wù)器時(shí)才訪問(wèn)復(fù)制的會(huì)話信息。 復(fù)制方式 復(fù)制提供了一些可能的好處,包括可用性、容錯(cuò)和伸縮性。此外,有大量會(huì)話復(fù)制的方法可用:方法的選擇取決于應(yīng)用程序群集的規(guī)模、復(fù)制的目標(biāo)和 servlet 容器支持的復(fù)制設(shè)施。復(fù)制有性能成本,包括 CPU 周期(存儲(chǔ)在會(huì)話中的序列化對(duì)象)、網(wǎng)絡(luò)帶寬(廣播更新),以及基于磁盤(pán)的方案中寫(xiě)入到磁盤(pán)或者數(shù)據(jù)庫(kù)的成本。 幾乎所有 servlet 容器都通過(guò)存儲(chǔ)在 HttpSession 中的序列化對(duì)象進(jìn)行 HttpSession 復(fù)制,所以假如是創(chuàng)建一個(gè)分布式應(yīng)用程序,應(yīng)當(dāng)確保只將可序列化對(duì)象放到會(huì)話中。(一些容器對(duì)像 EJB 引用、事務(wù)上下文、還有其他非可序列化的 J2EE 對(duì)象類型有非凡的處理。) 基于 JDBC 的復(fù)制 一種會(huì)話復(fù)制的方法是序列化會(huì)話內(nèi)容并將它寫(xiě)入數(shù)據(jù)庫(kù)。這種方法相當(dāng)直觀,其優(yōu)點(diǎn)是不僅會(huì)話可以故障轉(zhuǎn)移到其他主機(jī),而且即使整個(gè)群集失效,會(huì)話數(shù)據(jù)也可以保存下來(lái)。基于數(shù)據(jù)庫(kù)的復(fù)制的缺點(diǎn)是性能成本 —— 數(shù)據(jù)庫(kù)事務(wù)是昂貴的。雖然它可以在 Web 層很好地伸縮,但是它可能在數(shù)據(jù)層產(chǎn)生伸縮問(wèn)題 —— 假如群集增長(zhǎng)大到一定程度,擴(kuò)展數(shù)據(jù)層以容納會(huì)話數(shù)據(jù)會(huì)很困難或者成本無(wú)法接受。 基于文件的復(fù)制 基于文件的復(fù)制類似于使用數(shù)據(jù)庫(kù)存儲(chǔ)序列化的會(huì)話,只不過(guò)是使用共享文件服務(wù)器而不是數(shù)據(jù)庫(kù)來(lái)存儲(chǔ)會(huì)話數(shù)據(jù)。這種方式的成本一般比使用數(shù)據(jù)庫(kù)的成本(硬件成本、軟件許可證和計(jì)算開(kāi)銷)低,其代價(jià)則是可靠性(數(shù)據(jù)庫(kù)可提供比文件系統(tǒng)更強(qiáng)的持久化保證)。 基于內(nèi)存的復(fù)制 另一種復(fù)制方式是與群集中的一個(gè)或者多個(gè)其他服務(wù)器共享序列化的會(huì)話數(shù)據(jù)副本。復(fù)制所有會(huì)話到所有主機(jī)中提供了最大的可用性,并且負(fù)載平衡最輕易,但是因?yàn)閺?fù)制消息所消耗的每個(gè)節(jié)點(diǎn)的內(nèi)存和網(wǎng)絡(luò)帶寬,最終會(huì)限制群集的規(guī)模。一些應(yīng)用服務(wù)器支持與“伙伴(buddy)”節(jié)點(diǎn)的基于內(nèi)存的復(fù)制,其中每一個(gè)會(huì)話存在于主服務(wù)器上和一臺(tái)(或更多)備份服務(wù)器上。這種方案比將所有會(huì)話復(fù)制到所有服務(wù)器的伸縮性更好,但是當(dāng)需要將會(huì)話故障轉(zhuǎn)移到另一臺(tái)服務(wù)器上時(shí)會(huì)使負(fù)載平衡任務(wù)復(fù)雜化,因?yàn)樗仨氄页隽硗饽囊慌_(tái)(幾臺(tái))服務(wù)器有這個(gè)會(huì)話。 時(shí)間考慮 除了決定如何存儲(chǔ)復(fù)制會(huì)話數(shù)據(jù),還有什么時(shí)候復(fù)制數(shù)據(jù)的問(wèn)題。最可靠但也最昂貴的方法是每次數(shù)據(jù)改變時(shí)復(fù)制它(如每次 servlet 調(diào)用結(jié)束)。不那么昂貴、但是在故障時(shí)會(huì)有丟失一些數(shù)據(jù)的風(fēng)險(xiǎn)的方法是在每超過(guò) N 秒時(shí)復(fù)制數(shù)據(jù)。 與時(shí)間問(wèn)題有關(guān)的問(wèn)題是,是復(fù)制整個(gè)會(huì)話還是只試嘗復(fù)制會(huì)話中改變了的屬性(它包含的數(shù)據(jù)會(huì)少得多)。這些都需要在可靠性和性能之間進(jìn)行取舍。Servlet 開(kāi)發(fā)人員應(yīng)當(dāng)熟悉到在故障轉(zhuǎn)移時(shí),會(huì)話狀態(tài)可能變得“過(guò)時(shí)”(是幾次請(qǐng)求前的復(fù)制),并應(yīng)當(dāng)預(yù)備處理不是最新的會(huì)話內(nèi)容。(例如,假如一個(gè)interview 的第 3 步產(chǎn)生一個(gè)會(huì)話屬性,而用戶在第 4 步時(shí),請(qǐng)求被故障轉(zhuǎn)移到一個(gè)具有兩次請(qǐng)求之前的會(huì)話狀態(tài)復(fù)制的系統(tǒng)上,那么第 4 步的 servlet 代碼應(yīng)預(yù)備在會(huì)話中找不到這個(gè)屬性,并采取相應(yīng)的行動(dòng) —— 如重定向,而不是認(rèn)定它會(huì)在那里、并在找不到它時(shí)拋出一個(gè) NullPointerException。) 容器支持 Servlet 容器的 HttpSession 復(fù)制選項(xiàng)以及如何配置這些選項(xiàng)是各不相同的。IBM WebSphere? 提供的復(fù)制選項(xiàng)是最多的,它提供了在內(nèi)存中復(fù)制或者基于數(shù)據(jù)庫(kù)的復(fù)制、在 servlet 末尾或者基于時(shí)間的復(fù)制時(shí)間、傳播全部會(huì)話快照(JBoss 3.2 或以后版本)或者只傳播改變了的屬性等選擇?;趦?nèi)存的復(fù)制基于 JMS 發(fā)布-訂閱,它可以復(fù)制到所有克隆、一個(gè)“伙伴”復(fù)制品或者一個(gè)專門(mén)的復(fù)制服務(wù)器。 WebLogic 還提供了一組選擇,包括內(nèi)存中(使用一個(gè)伙伴復(fù)制品)、基于文件的或者基于數(shù)據(jù)庫(kù)的。JBoss 與 Tomcat 或者 Jetty servlet 容器一同使用時(shí),進(jìn)行基于內(nèi)存的復(fù)制,可以選擇 servlet 末尾或者基于時(shí)間的復(fù)制時(shí)間,而快照選項(xiàng)(在 JBoss 3.2 或以后版本)是只復(fù)制改變了的屬性。Tomcat 5.0 為所有群集節(jié)點(diǎn)提供了基于內(nèi)存的復(fù)制。此外,通過(guò)像 WADI 這樣的項(xiàng)目,可以用 servlet 過(guò)濾機(jī)制將會(huì)話復(fù)制添加到像 Tomcat 或者 Jetty 這樣的 servlet 容器中。 |
|
|