Vincent

          Vicent's blog
          隨筆 - 74, 文章 - 0, 評(píng)論 - 5, 引用 - 0
          數(shù)據(jù)加載中……

          Java 理論與實(shí)踐: 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 中。本月,專欄作家 Brian Goetz 分析了狀態(tài)復(fù)制的一些選項(xiàng)以及如何最有效地使用 HttpSession 以提供好的伸縮性和性能。在本文論壇中與本文作者和其他讀者分享您的觀點(diǎn)。(可以單擊文章頂部或者底部的 討論訪問論壇。)

          不管正在構(gòu)建的是 J2EE 還是 J2SE 服務(wù)器應(yīng)用程序,都有可能以某種方式使用 Java Servlet —— 可能是直接地通過像 JSP 技術(shù)、Velocity 或者 WebMacro 這樣的表示層,也可能通過一個(gè)基于 servlet 的 Web 服務(wù)實(shí)現(xiàn),如 Axis 或者 Glue。Servlet API 提供的一個(gè)最重要的功能是會(huì)話管理 —— 通過 HttpSession 接口進(jìn)行用戶狀態(tài)的認(rèn)證、失效和維護(hù)。

          會(huì)話狀態(tài)

          幾乎每一個(gè) Web 應(yīng)用程序都有一些會(huì)話狀態(tài),這些狀態(tài)有可能像記住您是否已登錄這么簡(jiǎn)單,也可能是您的會(huì)話的更詳細(xì)的歷史,如購(gòu)物車的內(nèi)容、以前查詢結(jié)果的緩存或者 20 頁(yè)動(dòng)態(tài)問卷表的完整響應(yīng)歷史。因?yàn)?HTTP 協(xié)議本身是無狀態(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ì)帶來嚴(yá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)買任何商品。噢)此外,使用 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ì)象是無狀態(tài)的,那么通常可以僅僅添加更多 Web 服務(wù)器來擴(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ī)范沒有要求 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)決定的。





          回頁(yè)首


          HttpSession API

          簡(jiǎn)單地說, HttpSession 接口支持幾種方法,servlet、JSP 頁(yè)或者其他表示層組件可以用這些方法來跨多個(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 {
              Object getAttribute(String s);
              Enumeration getAttributeNames();
              void setAttribute(String s, Object o);
              void removeAttribute(String s);
          
              boolean isNew();
              void invalidate();
              void setMaxInactiveInterval(int i);
              int getMaxInactiveInterval();
              ...
          }
          
          												
          										

          理論上,可以跨群集一致性地完全復(fù)制會(huì)話狀態(tài),這樣群集中的所有節(jié)點(diǎn)都可以服務(wù)任何請(qǐng)求,一個(gè)簡(jiǎn)單的負(fù)載平衡器可以以輪詢方式傳送請(qǐng)求,避開有故障的主機(jī)。不過,這種緊密的復(fù)制有很高的性能成本,并且難于實(shí)現(xiàn),當(dāng)群集接近某一規(guī)模時(shí),還會(huì)有伸縮性的問題。

          一種更常用的方式是將負(fù)載平衡與會(huì)話相似性(affinity) 結(jié)合起來 —— 負(fù)載平衡器可以將會(huì)話與連接相關(guān)聯(lián),并將會(huì)話中以后的請(qǐng)求發(fā)送給同一服務(wù)器。有很多硬件和軟件負(fù)載平衡器支持這個(gè)功能,并且這意味著只有主連接主機(jī)和會(huì)話需要故障轉(zhuǎn)移到另一臺(tái)服務(wù)器時(shí)才訪問復(fù)制的會(huì)話信息。





          回頁(yè)首


          復(fù)制方式

          復(fù)制提供了一些可能的好處,包括可用性、容錯(cuò)和伸縮性。此外,有大量會(huì)話復(fù)制的方法可用:方法的選擇取決于應(yīng)用程序群集的規(guī)模、復(fù)制的目標(biāo)和 servlet 容器支持的復(fù)制設(shè)施。復(fù)制有性能成本,包括 CPU 周期(存儲(chǔ)在會(huì)話中的序列化對(duì)象)、網(wǎng)絡(luò)帶寬(廣播更新),以及基于磁盤的方案中寫入到磁盤或者數(shù)據(jù)庫(kù)的成本。

          幾乎所有 servlet 容器都通過存儲(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)容并將它寫入數(shù)據(jù)庫(kù)。這種方法相當(dāng)直觀,其優(yōu)點(diǎn)是不僅會(huì)話可以故障轉(zhuǎn)移到其他主機(jī),而且即使整個(gè)群集失效,會(huì)話數(shù)據(jù)也可以保存下來。基于數(shù)據(jù)庫(kù)的復(fù)制的缺點(diǎn)是性能成本 —— 數(shù)據(jù)庫(kù)事務(wù)是昂貴的。雖然它可以在 Web 層很好地伸縮,但是它可能在數(shù)據(jù)層產(chǎn)生伸縮問題 —— 如果群集增長(zhǎng)大到一定程度,擴(kuò)展數(shù)據(jù)層以容納會(huì)話數(shù)據(jù)會(huì)很困難或者成本無法接受。

          基于文件的復(fù)制

          基于文件的復(fù)制類似于使用數(shù)據(jù)庫(kù)存儲(chǔ)序列化的會(huì)話,只不過是使用共享文件服務(wù)器而不是數(shù)據(jù)庫(kù)來存儲(chǔ)會(huì)話數(shù)據(jù)。這種方式的成本一般比使用數(shù)據(jù)庫(kù)的成本(硬件成本、軟件許可證和計(jì)算開銷)低,其代價(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ù)的問題。最可靠但也最昂貴的方法是每次數(shù)據(jù)改變時(shí)復(fù)制它(如每次 servlet 調(diào)用結(jié)束)。不那么昂貴、但是在故障時(shí)會(huì)有丟失一些數(shù)據(jù)的風(fēng)險(xiǎn)的方法是在每超過 N 秒時(shí)復(fù)制數(shù)據(jù)。

          與時(shí)間問題有關(guān)的問題是,是復(fù)制整個(gè)會(huì)話還是只試嘗復(fù)制會(huì)話中改變了的屬性(它包含的數(shù)據(jù)會(huì)少得多)。這些都需要在可靠性和性能之間進(jìn)行取舍。Servlet 開發(fā)人員應(yīng)當(dāng)認(rèn)識(shí)到在故障轉(zhuǎn)移時(shí),會(huì)話狀態(tài)可能變得“過時(shí)”(是幾次請(qǐng)求前的復(fù)制),并應(yīng)當(dāng)準(zhǔn)備處理不是最新的會(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 。)





          回頁(yè)首


          容器支持

          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è)專門的復(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ù)制。此外,通過像 WADI 這樣的項(xiàng)目,可以用 servlet 過濾機(jī)制將會(huì)話復(fù)制添加到像 Tomcat 或者 Jetty 這樣的 servlet 容器中。





          回頁(yè)首


          改進(jìn)分布式 Web 應(yīng)用程序的性能

          不管決定使用什么機(jī)制進(jìn)行會(huì)話復(fù)制,可以用幾種方式改進(jìn) Web 應(yīng)用程序的性能和伸縮性。首先記住,為了獲得會(huì)話復(fù)制的好處,需要在部署描述符中將 Web 應(yīng)用程序標(biāo)記為 distributable,并保證在會(huì)話中的所有內(nèi)容都是可序列化的。

          保持會(huì)話最小

          因?yàn)閺?fù)制會(huì)話有隨著會(huì)話中的對(duì)象圖(object graph) 的變大而增加成本,所以應(yīng)當(dāng)盡可能地在會(huì)話中少放置數(shù)據(jù)。這樣做會(huì)減少?gòu)?fù)制的序列化的開銷、網(wǎng)絡(luò)帶寬要求和磁盤要求。特別地,將共享對(duì)象存儲(chǔ)在會(huì)話中一般不是好主意,因?yàn)樗鼈冃枰獜?fù)制到它們所屬的 每一個(gè)會(huì)話中。

          不要繞過 setAttribute

          在改變會(huì)話的屬性時(shí),要知道即使 servlet 容器只是試圖做最小的更新(只傳播改變了的屬性),如果沒有調(diào)用 setAttribute ,容器也可能沒有注意到已經(jīng)改變的屬性。(想像在會(huì)話中有一個(gè) Vector ,表示購(gòu)物車中的商品 —— 如果調(diào)用 getAttribute() 獲取 Vector 、然后向它添加一些內(nèi)容,并且不再次調(diào)用 setAttribute ,容器可能不會(huì)意識(shí)到 Vector 已經(jīng)改變了。)

          使用細(xì)化的會(huì)話屬性

          對(duì)于支持最小更新的容器,可以通過將多個(gè)細(xì)化的對(duì)象而不是一個(gè)大塊頭放到會(huì)話中而降低會(huì)話復(fù)制的成本。這樣,對(duì)快速改變的數(shù)據(jù)的改變也不會(huì)迫使容器去序列化并傳播慢速改變的數(shù)據(jù)。

          完成后使之失效

          如果知道用戶完成了會(huì)話的使用(如,用戶選擇注銷登錄),確保調(diào)用 HttpSession.invalidate() 。否則,會(huì)話將持久化直到它失效,這會(huì)消耗內(nèi)存,并且可能是長(zhǎng)時(shí)間的(取決于會(huì)話超時(shí)時(shí)間)。許多 servlet 容器對(duì)可以跨所有會(huì)話使用的內(nèi)存的數(shù)量有一個(gè)限制,達(dá)到這個(gè)限制時(shí),會(huì)序列化最先使用的會(huì)話并將它寫到磁盤上。如果知道用戶使用完了會(huì)話,可以使容器不再處理它并使它作廢。

          保持會(huì)話干凈

          如果在會(huì)話中有大的項(xiàng),并且只在會(huì)話的一部分中使用,那么當(dāng)不再需要時(shí)應(yīng)刪除它們。刪除它們會(huì)減少會(huì)話復(fù)制的成本。(這種做法類似于使用顯式 nulling 以幫助垃圾收集器,老讀者知道我一般不建議這樣做,但是在這種情況下,因?yàn)橛袕?fù)制,在會(huì)話中保持垃圾的成本要高得多,因此值得以這種方式幫助容器。)





          回頁(yè)首


          結(jié)束語

          通過 HttpSession 復(fù)制,Servlet 容器可以在構(gòu)建復(fù)制的、高可用性的 Web 應(yīng)用程序方面給您減輕很多負(fù)擔(dān)。不過,對(duì)于復(fù)制有一些配置選項(xiàng),每個(gè)容器都不一樣,復(fù)制策略的選擇對(duì)于應(yīng)用程序的容錯(cuò)、性能和伸縮性有影響。復(fù)制策略的選擇不應(yīng)當(dāng)是事后的 —— 您應(yīng)當(dāng)在構(gòu)建 Web 應(yīng)用程序時(shí)就考慮它。并且,一定不要忘記進(jìn)行負(fù)載測(cè)試以確定應(yīng)用程序的伸縮性 —— 在客戶替您做之前。

          posted on 2006-08-24 17:41 Binary 閱讀(218) 評(píng)論(0)  編輯  收藏 所屬分類: j2se

          主站蜘蛛池模板: 武隆县| 高雄县| 商洛市| 湟中县| 漳州市| 阳东县| 岑巩县| 碌曲县| 贵南县| 桐梓县| 平原县| 磴口县| 马边| 工布江达县| 双城市| 军事| 亳州市| 勐海县| 本溪市| 沛县| 湖南省| 黄梅县| 东乌| 突泉县| 义乌市| 吴川市| 旺苍县| 弥勒县| 迁西县| 伊吾县| 彰武县| 综艺| 图木舒克市| 商都县| 张家川| 汉中市| 巴东县| 额尔古纳市| 营口市| 喀什市| 新乡县|