本文目的在于分析Jetspeed支持集群的現狀。首先介紹了集群計算的背景知識,然后使用tomcat作為例子配置了一個(gè)集群,接著(zhù)分析了 jetspeed對集群的支持現狀,提出了解決這些問(wèn)題的辦法,最后詳細解釋了jetspeed保存sesson數據的操作,這將對jetspeed的改造有幫助。
1 、 集群背景介紹
1.1 術(shù)語(yǔ)定義
服務(wù)軟體是b/s或c/s結構的s部分,是為b或c提供服務(wù)的服務(wù)性軟件系統。
服務(wù)硬體指提供計算服務(wù)的硬件、比如pc機、pc服務(wù)器。
服務(wù)實(shí)體通指服務(wù)軟體和服務(wù)硬體。
客戶(hù)端指接受服務(wù)實(shí)體服務(wù)的軟件或硬件。
1.2 兩大關(guān)鍵特性
集群是一組協(xié)同工作的服務(wù)實(shí)體,用以提供比單一服務(wù)實(shí)體更具擴展性與可用性的服務(wù)平臺。在客戶(hù)端看來(lái),一個(gè)集群就象是一個(gè)服務(wù)實(shí)體,但事實(shí)上集群由一組服務(wù)實(shí)體組成。與單一服務(wù)實(shí)體相比較,集群提供了以下兩個(gè)關(guān)鍵特性:
可擴展性--集群的性能不限于單一的服務(wù)實(shí)體,新的服務(wù)實(shí)體可以動(dòng)態(tài)地加入到集群,從而增強集群的性能。
高可用性--集群通過(guò)服務(wù)實(shí)體冗余使客戶(hù)端免于輕易遇到out of service的警告。在集群中,同樣的服務(wù)可以由多個(gè)服務(wù)實(shí)體提供。如果一個(gè)服務(wù)實(shí)體失敗了,另一個(gè)服務(wù)實(shí)體會(huì )接管失敗的服務(wù)實(shí)體。集群提供的從一個(gè)出錯的服務(wù)實(shí)體恢復到另一個(gè)服務(wù)實(shí)體的功能增強了應用的可用性。
1.3 兩大能力
為了具有可擴展性和高可用性特點(diǎn),集群的必須具備以下兩大能力:
負載均衡--負載均衡能把任務(wù)比較均衡地分布到集群環(huán)境下的計算和網(wǎng)絡(luò )資源。
錯誤恢復--由于某種原因,執行某個(gè)任務(wù)的資源出現故障,另一服務(wù)實(shí)體中執行同一任務(wù)的資源接著(zhù)完成任務(wù)。這種由于一個(gè)實(shí)體中的資源不能工作,另一個(gè)實(shí)體中的資源透明的繼續完成任務(wù)的過(guò)程叫錯誤恢復。
負載均衡和錯誤恢復都要求各服務(wù)實(shí)體中有執行同一任務(wù)的資源存在,而且對于同一任務(wù)的各個(gè)資源來(lái)說(shuō),執行任務(wù)所需的信息視圖(信息上下文)必須是一樣的。
1.4 兩大技術(shù)
實(shí)現集群務(wù)必要有以下兩大技術(shù):
集群地址--集群由多個(gè)服務(wù)實(shí)體組成,集群客戶(hù)端通過(guò)訪(fǎng)問(wèn)集群的集群地址獲取集群內部各服務(wù)實(shí)體的功能。具有單一集群地址(也叫單一影像)是集群的一個(gè)基本特征。維護集群地址的設置被稱(chēng)為負載均衡器。負載均衡器內部負責管理各個(gè)服務(wù)實(shí)體的加入和退出,外部負責集群地址向內部服務(wù)實(shí)體地址的轉換。有的負載均衡器實(shí)現真正的負載均衡算法,有的只支持任務(wù)的轉換。只實(shí)現任務(wù)轉換的負載均衡器適用于支持ACTIVE-STANDBY的集群環(huán)境,在那里,集群中只有一個(gè)服務(wù)實(shí)體工作,當正在工作的服務(wù)實(shí)體發(fā)生故障時(shí),負載均衡器把后來(lái)的任務(wù)轉向另外一個(gè)服務(wù)實(shí)體。
內部通信--為了能協(xié)同工作、實(shí)現負載均衡和錯誤恢復,集群各實(shí)體間必須時(shí)常通信,比如負載均衡器對服務(wù)實(shí)體心跳測試信息、服務(wù)實(shí)體間任務(wù)執行上下文信息的通信。
具有同一個(gè)集群地址使得客戶(hù)端能訪(fǎng)問(wèn)集群提供的計算服務(wù),一個(gè)集群地址下隱藏了各個(gè)服務(wù)實(shí)體的內部地址,使得客戶(hù)要求的計算服務(wù)能在各個(gè)服務(wù)實(shí)體之間分布。內部通信是集群能正常運轉的基礎,它使得集群具有均衡負載和錯誤恢復的能力。
2 集群配置
從上圖可知,由服務(wù)實(shí)體1、服務(wù)實(shí)體2和負載均衡器組成了一個(gè)集群。服務(wù)實(shí)體1和服務(wù)實(shí)體2參與對客戶(hù)端的服務(wù)支持工作,均衡負載器為客戶(hù)端維護集群的單一影像。集群實(shí)體間通過(guò)內部的通信網(wǎng)交流信息,這種交流機制一般采用組播協(xié)議。負載均衡器通過(guò)內部通信網(wǎng)探測各服務(wù)實(shí)體的心跳信息,服務(wù)實(shí)體間通過(guò)內部通信網(wǎng)完成任務(wù)資源的傳播??梢钥闯?,配置集群主要由配置服務(wù)實(shí)體和配置負載均衡器兩部分組成。本文使用tomcat 4.12、apache 2.0.43配置集群環(huán)境,相關(guān)軟件的部署圖如下:
服務(wù)實(shí)體1/2,負載均衡器可以部署在不同的機器上,也可以在同一機器上,本文環(huán)境為同一機器。
2.1 準備軟件
tomcat是開(kāi)源servlet \jsp服務(wù)器,下載地點(diǎn)http://jakarta.apache.org/ ;
apache 2.0.43 是開(kāi)源的www服務(wù)器,下載地點(diǎn)http://www.apache.org/dist/httpd/binaries/ ;
JavaGroups是一個(gè)實(shí)現集群服務(wù)實(shí)體間通信的通信協(xié)議,下載地址:http://www.javagroups.com/ ;
Tomcat 會(huì )話(huà)復制庫,基于JavaGroups通信協(xié)議,完成集群服務(wù)實(shí)體間任務(wù)執行上下文的復制,下載地址: http://www.filip.net/tomcat/tomcat-javagroups.jar
jk2 模塊,jk 是mod_jserv的替代者,它是Tomcat-Apache插件,處理Tomcat和Apache之間的通信,在集群配置中充當負載均衡器的作用。 JK2是符合apache 2.x系列的新品,下載地址:http://jakarta.apache.org/builds/jakarta-tomcat- connectors/jk2/release/v2.0.2/bin/ 。
2.2 配置負載均衡器
在apache下配置負載均衡器分為三步,注意每次修改httpd.conf和workers2.properties時(shí)不要忘了重新啟動(dòng)apache。
第一步,安裝和調試apache
負載均衡器jk2模塊是apache www 服務(wù)的插件,所以配置負載均衡器就得先安裝apache。本文下載的是windows版本 2.0.43,執行setup.exe并回答一些簡(jiǎn)單問(wèn)題就可完成apache的任務(wù)。值得注意的是,安裝并啟動(dòng)apache后如果apache對 http://localhost/ 地址沒(méi)反應,你得修改apache安裝路徑下htdocs目錄下的index.html.xx文件,比如把index.html.en改成 index.html。
第二步,安裝jk2
把下載到的mod_jk2-2.0.43.dll 改成mod_jk2.dll 放到apache的modules目錄下,修改apache的httpd.conf,即在LoadModule foo_module modules/mod_foo.so 行下插入mod_jk2模塊的裝載信息:
# Example:
# LoadModule foo_module modules/mod_foo.so
#
LoadModule jk2_module modules/mod_jk2.dll
第三步,配置jk2
jk2的配置全在一個(gè)配置文件中,文件名為workers2.properties,和apache 的httpd.conf放在同一個(gè)目錄下。以下是這個(gè)文件的內容:
#++++++++++++++++++++++++++++++++++++
# only at beginnin. In production uncomment it out
[logger.apache2]
level=DEBUG
#shm必須配
[shm]
file=D:\Program Files\Apache Group\Apache2\logs\shm.file
size=1048576
# 第一個(gè)tomcat 的地址
# Example socket channel, override port and host.
[channel.socket:tomcat1]
port=11009
host=127.0.0.1
# 定義第一個(gè)工作者指向第一個(gè)tomcat
# define the worker
[ajp13:tomcat1]
channel=channel.socket:tomcat1
#第二個(gè)tomcat 得地址
# Example socket channel, override port and host.
[channel.socket:tomcat2]
port=12009
host=10.1.36.123
# 定義第二個(gè)工作者指向第二個(gè)tomcat
# define the worker
[ajp13:tomcat2]
channel=channel.socket:tomcat2
#定義負載均衡器,使其包含兩個(gè)工作者
[lb:lb1]
worker=ajp13:tomcat2
worker=ajp13:tomcat1
#指定負載均衡器完成單一地址映射,使得apache 服務(wù)所在的uri全部指向 兩個(gè)#tomcat 上的 root Uri mapping
[uri:/*]
group=lb:lb1
#++++++++++++++++++++++++++++++++++++++++++
對于jk2模塊的負載均衡配置可參見(jiàn)相關(guān)站點(diǎn),值得提及的是jk2的負載均衡還支持權重分配等優(yōu)秀功能。
2.3 配置tomcat
同屬于一個(gè)集群下的兩個(gè)服務(wù)實(shí)體,要求功能的同一性,所以我們可先安裝和配置第一個(gè)tomcat,接著(zhù)拷貝形成第二個(gè)tomcat,最后配置第二個(gè)tomcat。
2.3.1 安裝第一個(gè)tomcat
安裝tomcat 非常簡(jiǎn)單,本文就不再描述。我們假設第一個(gè)tomcat的安裝路徑為d:\tomcat1。
拷貝tomcat-javagroups.jar和javagroups.jar到d:\tomcat1\ server\lib 路徑下。
2.3.2 配置第一個(gè)tomcat
2.3.2.1 配置jk2
tomcat 中的jk2 connector缺省端口為8009,為了在一臺機器上運行兩個(gè)tomcat,修改D:\Tomcat1\conf\jk2.properties,設置jk2 connector的端口為11009,整個(gè)文件內容如下:
#++++++++++++++
channelSocket.port=11009
#++++++++++++++
2.3.2.2 修改server.conf
首先為了讓一臺機器上運行兩個(gè)tomcat,修改server.conf的tomcat 停止指令監聽(tīng)端口:
然后打開(kāi)JK2 AJP connector ,關(guān)閉其它c(diǎn)onnector,下面是JK2 AJP 1.3的樣子,這里已把它的端口改為11009:
port="11009" minProcessors="5" maxProcessors="75"
enableLookups="true" redirectPort="8443"
acceptCount="10" debug="0" connectionTimeout="20000"
useURIValidationHack="false"
protocolHandlerClassName="org.apache.jk.server.JkCoyoteHandler"/>
接著(zhù)配置需要集群支持的webapp(比如examples) 的context,添加如下manager:
className="org.apache.catalina.session.InMemoryReplicationManager"
protocolStack="UDP(mcast_addr=228.1.2.3;mcast_port=45566;ip_ttl=32):PING(timeout=3000;
num_initial_members=6):FD(timeout=5000):VERIFY_SUSPECT(timeout=1500):
pbcast.STABLE(desired_avg_gossip=10000):pbcast.NAKACK(gc_lag=10;
retransmit_timeout=3000):UNICAST(timeout=5000;min_wait_time=2000):
MERGE2:FRAG:pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;
shun=false;print_local_addr=false)">
注意protocolStack的值必須在一行內寫(xiě)完。
2.3.3 配置第二個(gè)tomcat
我們先把已經(jīng)配好的第一個(gè)tomcat復制一份,形成第二個(gè)tomcat,假設路徑為d:\tomcat2。
2.3.3.1 配置jk2
修改D:\Tomcat2\conf\jk2.properties,設置jk2 connector的端口12009,整個(gè)文件內容如下:
#++++++++++++++
channelSocket.port=12009
#++++++++++++++
2.3.3.2 修改server.conf
有了第一個(gè)tomcat的配置我們只需修改server.conf的tomcat 停止指令監聽(tīng)端口:
然后設置JK2 AJP connector 端口為12009。
2.4 運行測試
啟動(dòng)apache,tomcat1和tomcat2。
2.4.1 測試負載均衡
我們先準備兩個(gè)文件,第一個(gè)文件為test.jsp,拷貝到第一個(gè)tomcat 的根web應用的目錄即d:\tomcat1\webapps\ROOT 下:
Tomcat 1
第二個(gè)文件也為test.jsp,拷貝到第二個(gè)tomcat 的根web應用的目錄即d:\tomcat2\webapps\ROOT 下:
Tomcat 2
從不同的瀏覽器中多次輸入地址http://localhost/test.jsp 會(huì )看到不同的顏色,這表明apache中的jk2模塊起到了負載均衡的作用。
2.4.2 測試錯誤恢復
訪(fǎng)問(wèn)url: http://localhost/examples/servlet/SessionExample 可以得到一個(gè)關(guān)于session的例子,我們用它來(lái)測試集群的錯誤恢復能力。
測試步驟如下:
關(guān)閉tomcat1和tomcat2;
啟動(dòng)tomcat1
在瀏覽器中輸入屬性名tomcat1和屬性值tomcat1再提交,返回的頁(yè)面顯示session中有剛剛輸入的tomcat1屬性;
啟動(dòng)tomcat2;
過(guò)一會(huì )后(等待tomcat2和tomcat1通信并復制信息)關(guān)閉tomcat1;
在瀏覽器中輸入屬性名tomcat2和屬性值tomcat2再提交,返回的頁(yè)面顯示session中有剛剛輸入的tomcat2屬性,還有先前輸入的tomcat1屬性;
啟動(dòng)tomcat1;
過(guò)一會(huì )后(等待tomcat2和tomcat1通信并復制信息)關(guān)閉tomcat2;
在瀏覽器中輸入屬性名tomcat11和屬性值tomcat11再提交,返回的頁(yè)面顯示session中有剛剛輸入的tomcat11屬性,還有先前輸入的tomcat1和tomcat2屬性;
……
2.4.3 測試多目傳輸的方法
如果運行測試失敗,可以使用下面的JAVAGROUP方法測試機器的多目傳輸性:
啟動(dòng)多目接收器:
java org.javagroups.tests.McastReceiverTest -mcast_addr 224.10.10.10 -port 5555
啟動(dòng)多目傳輸器:
java org.javagroups.tests.McastSenderTest -mcast_addr 224.10.10.10 -port 5555
這樣你在McastSenderTest窗口中輸入內容,應該在McastReceiverWindow中可以看到結果。如果看不到結果,在 McastSenderTest運行參數中加入-ttl 32,如果還不行,可以修改多目地址再試試(注意避開(kāi)系統保留用的多目地址);如果還不行,就去問(wèn)問(wèn)網(wǎng)管吧!
2.4.4 對tomcat-javagroups的修改
tomcat-javagroups.jar中的org.apache.catalina.session.ReplicatedSession類(lèi)的removeAttribute方法會(huì )導致stackoverflow錯誤,請按下面的代碼對其進(jìn)行修改:
public void removeAttribute(String name, boolean notify, boolean jgnotify) {
super.removeAttribute(name);
if ( jgnotify )
{
SessionMessage msg =
new SessionMessage(notify?SessionMessage.EVT_ATTRIBUTE_REMOVED_WNOTIFY:SessionMessage. EVT_ATTRIBUTE_REMOVED_WONOTIFY,
null,
getId(),
name,
null,
null);
sendMessage(msg);
}
}
public void removeAttribute(String name, boolean notify) {
removeAttribute(name,notify,true);
}
3 jetspeed集群
我們現在知道了如何配置、甚至擁有一個(gè)集群環(huán)境,接下來(lái)本文分析Jetspeed的集群現狀,主要包括repository和Session數據;為了使分析具有目的,在分析Jetspeed的集群現狀之前,先講述了集群需求和RunData對象。讀者可以用集群環(huán)境來(lái)驗證和調試Jetspeed的集群功能。
3.1 集群要求
《Memory Session Replication》一文中講述了支持集群的應用程序需注意的要點(diǎn),現在對關(guān)于應用系統開(kāi)發(fā)時(shí)應注意的事項總結如下:
保存在Session中的對象必須實(shí)現java.io.Serializable接口;
從session中獲取對象修改后必須用session.setAttribute方法重置session中的屬性,因為只有setAttribute能導致session復制。
Java VM不支持類(lèi)變量的序列化,所以要注意failover不能依賴(lài)類(lèi)變量;
保證各個(gè)服務(wù)實(shí)體的配置完全一樣;
保證session狀態(tài)是唯一決定當前任務(wù)狀態(tài)的東西,臨時(shí)文件、類(lèi)變量等會(huì )使得錯誤恢復難以實(shí)現、行為可能琢磨不定;
利用request.setAttribute()保存當前請求級的狀態(tài),減少服務(wù)實(shí)體間通信次數。
盡量不要在session中保存大對象,提高服務(wù)實(shí)體間通信性能。
3.2 RunData對象
RunData 對象概念來(lái)自于Turbine,在Jetspeed中RunData對象的類(lèi)型是DefaultJetspeedRunData,這個(gè)類(lèi)擴展了 Turbine中的DefaultTurbineRunData類(lèi)。Jetspeed系統接到用戶(hù)瀏覽器的URL請求,進(jìn)行計算和信息處理,最后返回給瀏覽器HTTP代碼流的整個(gè)過(guò)程中的代碼都可以訪(fǎng)問(wèn)同一個(gè)RunData對象。所以RunData對象是Jetspeed系統中各個(gè)代碼模塊共享信息的機制。
3.3 Jetspeed的Repository
Repository 一般指一個(gè)軟件系統賴(lài)以啟動(dòng)、運行的持久性環(huán)境,包括啟動(dòng)Repository和運行Repository兩部分。啟動(dòng)Repository用于決定系統啟動(dòng)時(shí)的參數,系統運行時(shí)不會(huì )改變它,如果改變了這些參數,軟件系統必須重新啟動(dòng);運行Repository指實(shí)時(shí)影響軟件系統業(yè)務(wù)操作的參數,這些參數可以被用戶(hù)或管理員當系統在線(xiàn)時(shí)改變?,F在的趨勢是:盡量減少啟動(dòng)Repository,而擴大運行Repository;針對Repository的修改最好能使用管理性框架,比如SNMP和JMX。Jetspeed的repository主要在Xreg、psml和Properties文件中實(shí)現。
Xreg是jetspeed的注冊表,用于登記portlet、control、controller、skin、mediatype等原始資源的定義,jetspeed中缺省地把它實(shí)現為文件形式,各種類(lèi)型有自己的注冊表文件;
Psml 是門(mén)戶(hù)結構標記語(yǔ)言的簡(jiǎn)稱(chēng),用于組織xreg中的原始資源形成一個(gè)對門(mén)戶(hù)視圖的定義,當用戶(hù)使用桌面瀏覽器訪(fǎng)問(wèn)jetspeed系統時(shí),這個(gè)系統根據用戶(hù)的URL定位一個(gè)Psml文檔,接著(zhù)解釋這個(gè)文檔形成HTML代碼流返回給瀏覽器,瀏覽器展現這個(gè)代碼流從而形成視窗化的門(mén)戶(hù)視圖。Jetspeed中包括了對psml的數據庫和文件兩種實(shí)現方式;
Properties定義了Jetspeed的重要服務(wù)及其參數,目前只有文件實(shí)現方式。
Jetspeed 的啟動(dòng)Repository主要在Properties文件中,運行Repository在xreg和psml中。文件形式的實(shí)現大大阻礙了 jetspeed支持集群的能力和表現,因為現在很少的應用服務(wù)器集群能在一個(gè)文件系統上運行,如果Repository需要在運行時(shí)改變,就必須同步多個(gè)服務(wù)實(shí)體上的文件,這是一個(gè)相當麻煩的問(wèn)題。如果Repository支持數據庫實(shí)現形式,Jetspeed可以充分利用數據庫的存儲和同步機制實(shí)現同一個(gè)Repository服務(wù)于多個(gè)Jetspeed。所以要想 jetspeed支持集群、擁有更佳表現,對Repository的數據庫化是一個(gè)不可忽視的任務(wù)。
支持數據庫的集群配置如下圖:
這個(gè)圖顯示了在數據庫集群環(huán)境下的jetspeed集群配置,數據庫負載均衡器實(shí)現數據庫集群的單一影像,例子有weblogic server中的multipool datasource,sql server 基于的windows 2000集群的單一集群IP,ORACLE RAC 的支持多連接地址的thin jdbc driver。
3.4 Jetspeed的Session數據
支持集群必須使得各個(gè)服務(wù)實(shí)體針對某個(gè)任務(wù)的執行環(huán)境是相同的,對于jetspeed來(lái)說(shuō)就是針對各個(gè)URL請求,session的數據能在各個(gè) jetspeed上復制。這些session被同一個(gè)sessionid所標識,這些標識可能來(lái)自瀏覽器的cookies或URL中。我們首先用一個(gè) velocityportlet來(lái)顯示Jetspeed的session中到底保存了什么數據,這個(gè)portlet的注冊名字為 SessionPortlet。
3.4.1 SessionPortlet
SessionPortlet是一個(gè) velocityPortlet,其類(lèi)名可以是CustomizerVelocityPortlet或 VelocityPortlet,一般情況下沒(méi)有必要開(kāi)發(fā)一個(gè)新的portlet class。關(guān)于如何開(kāi)發(fā)部署portlet的教程可見(jiàn)參考部分,現在我們分注冊、控制助手、portlet模版和運行來(lái)講述這個(gè)portlet。
3.4.1.1 注冊
SessionPortlet用于顯示目前的session數據。它在xreg中的注冊代碼為:
parent="CustomizerVelocity" application="false">
check infomation in session
org.apache.jetspeed.portal.portlets.CustomizerVelocityPortlet
cachedOnName="true" cachedOnValue="true"/>
hidden="true" cachedOnName="true" cachedOnValue="true"/>
legend
velocity.legend
3.4.1.2 控制助手Action
portlets.SessionAction是Velocityportlet模版portlet的控制助手,在velocity解釋模版前執行:
public class SessionAction extends VelocityPortletAction {
protected void buildNormalContext( VelocityPortlet portlet,
Context context,
RunData rundata )
{
Map map = new HashMap();
Enumeration enumeration = rundata.getSession().getAttributeNames();
while (enumeration.hasMoreElements()) {
Object key = (Object) enumeration.nextElement();
Object value = (Object)rundata.getSession().getAttribute(key.toString());
map.put(key, value);
}
context.put("sessions",map);
}
}
從上面的代碼可以看出,這個(gè)控制助手在模版的模型(MVC中的M)環(huán)境中設置了一個(gè)保存了session數據的map數據結構。
3.4.1.3 portlet模版
SessionPortlet的模版文件是session.vm(MVC中的V),這個(gè)文件的內容如下:
#foreach( $key in $sessions.keySet() )
- Key: $key -> Value: $sessions.get($key)
#end
3.4.1.4 定制psml和運行SessionPortlet
用admin/jetspeed 或turbine/turbine賬號/口令登錄到j(luò )etspeed系統后,可以在velocity.legend portlet分類(lèi)中找到SessionPortlet,把它加入到你的psml中后可以看到SessionPortlet顯示的session數據(你可以多多點(diǎn)擊其它的URL,盡量地使jetspeed在session中多放一些數據):
從上面的session快照可以看出,Jetspeed的session數據主要分為兩類(lèi):BaseJetspeedUser和JetspeedHttpStateManagerService$StateEntry,下面我們就分別來(lái)看看這兩個(gè)類(lèi)的情況。
3.4.2 BaseJetspeedUser
我們從《Session數據類(lèi)圖(部分)》可以看出BaseJetspeedUser實(shí)現了serializable接口。另外分析這個(gè)類(lèi)及其父類(lèi)的代碼可了解到這個(gè)類(lèi)的成員也實(shí)現了serializable接口。所以可以初步得出這個(gè)類(lèi)是集群安全的。
DefaultTurbineRundata實(shí)現了這個(gè)類(lèi)型的session數據的操作接口:
保存user對象到session中,這個(gè)方法登錄后由TurbineAuthentication的login調用,登錄前由 JetspeedSessionValidator的doPerform調用,它們同時(shí)會(huì )調用DefaultTurbineRundata的 setUser方法:
public void save()
{
session.putValue(User.SESSION_KEY, (Object) user );
}
public void setUser(User user)
{
this.user = user;
}
從session中獲取user對象數據,這個(gè)方法由JetspeedSessionValidator的doPerform調用:
public void populate()
{
user = getUserFromSession();
if ( user != null )
{
user.setLastAccessDate();
user.incrementAccessCounter();
user.incrementAccessCounterForSession();
}
}
public User getUserFromSession()
{
return getUserFromSession(session);
}
public static User getUserFromSession(HttpSession session)
{
try
{
return (User) session.getValue(User.SESSION_KEY);
}
catch ( ClassCastException e )
{
return null;
}
}
刪除session中的用戶(hù)數據,目前沒(méi)地方調用:
public boolean removeUserFromSession()
{
return removeUserFromSession(session);
}
public static boolean removeUserFromSession(HttpSession session)
{
try
{
session.removeValue(User.SESSION_KEY);
}
catch ( Exception e )
{
return false;
}
return true;
}
.4.2.1 用戶(hù)登錄
用戶(hù)在jetspeed的首頁(yè)中輸入用戶(hù)名和口令,接著(zhù)點(diǎn)擊登錄(login)按鈕,可以激活JLoginUser.doPerfom-> TurbineAuthentication.login->DefaultTurbineRundata.save->JetspeedSessionValidator.doPerform-> DefaultTurbineRundata.populate系列步驟。
如果properties配置中的配置項 automatic.logon.enable 的值為true,JLoginUser.doPerfom還會(huì )設置瀏覽器cookies:username 和logincookie。username是成功登錄的用戶(hù)名, logincookie是一個(gè)隨機值,會(huì )保存到用戶(hù)數據庫中。
當用戶(hù)訪(fǎng)問(wèn)jetspeed的首頁(yè)時(shí),JetspeedSessionValidator.doPerform檢查RunData對象中的當前用戶(hù),如果沒(méi)有登錄而且automatic.logon.enable 的值為true,它會(huì )從cookies中獲取username 和logincookie,再從用戶(hù)數據庫中查尋用戶(hù)的logincookie,如果它們相等則調用下面的代碼設置RunData的用戶(hù)數據:
data.setUser(user);
user.setHasLoggedIn(new Boolean(true));
user.updateLastLogin();
data.save();
至于針對不同的用戶(hù),首頁(yè)中顯示的portlet由缺省screen模版中調用JetspeedTool的方法(有一套PSML定位算法)來(lái)決定。
3.4.2.2 當session過(guò)期之后顯示匿名用戶(hù)的主頁(yè)
當session 過(guò)期,Turbine.doget首先會(huì )創(chuàng )建新的session,接著(zhù)激活 JetspeedSessionValidator.doPerform-> JetspeedSecurity.getAnonymousUser->DefaultTurbineRundata.save系列步驟。
JetspeedSessionValidator.doPerform會(huì )設置缺省screen模版。
3.4.2.3 用戶(hù)登出
當用戶(hù)登錄之后,點(diǎn)擊Jetspeed系統右上角的登出(logout)按鈕,可以激活JLogOut.doPerform-> TurbineAuthentication.logout-> TurbineAuthentication.getAnonymousUser-> DefaultTurbineRundata.save系列步驟。
TurbineAuthentication.getAnonymousUser從數據庫中得到匿名用戶(hù)的用戶(hù)數據(根據properties配置中user.anonymous項)。
如果properties配置中配置項automatic.logon.enable 的值為true,JLogOut.doPerform還會(huì )刪除瀏覽器和當前request的cookies:username 和logincookie,防止后面的JetspeedSessionValidator拿著(zhù)先前的用戶(hù)數據自動(dòng)登錄。 JLogOut.doPerform最后設置data的缺省screen模版。
3.4.3 JetspeedHttpStateManagerService$StateEntry
我們從《Session數據的類(lèi)圖(部分)》可以看出StateEntry沒(méi)有實(shí)現了Serializable接口。把它放到session的屬性中不是集群安全的。Serializable接口只是個(gè)標志接口,它不擁有任何函數和數據成員,
為了使其集群安全化,首先必須讓StateEntry實(shí)現Serializable接口。
DefaultJetspeedRunData擁有下列對StateEntry類(lèi)型的session數據操作接口:
用戶(hù)session接口,獲取保存用戶(hù)session數據的SessionState。這個(gè)SessionState保存的session數據以 "org.apache.jetspeed.services.statemanager.JetspeedHttpStateManagerService"+ sessionID為key。
public SessionState getUserSessionState()
{
StateManagerService service = (StateManagerService)TurbineServices
.getInstance().getService(StateManagerService.SERVICE_NAME);
if (service == null) return null;
return service.getSessionState(getSession().getId());
}
request 的Session接口,獲取保存當前request session數據的SessionState。這個(gè)SessionState保存的session數據以 "org.apache.jetspeed.services.statemanager.JetspeedHttpStateManagerService"+ (sessionID和profileID組成的pageSessionID)為key。
public SessionState getPageSessionState()
{
StateManagerService service = (StateManagerService)TurbineServices
.getInstance().getService(StateManagerService.SERVICE_NAME);
if (service == null) return null;
return service.getSessionState(getPageSessionId());
}
Portlet 的Session接口,獲取保存Portlet session數據的SessionState。這個(gè)SessionState保存的session數據以 "org.apache.jetspeed.services.statemanager.JetspeedHttpStateManagerService"+ pageSessionID+portletID為key。
public SessionState getPortletSessionState(String id)
{
// get the StateManagerService
StateManagerService service = (StateManagerService)TurbineServices
.getInstance().getService(StateManagerService.SERVICE_NAME);
if (service == null) return null;
String pageInstanceId = getPageSessionId();
return service.getSessionState(pageInstanceId + id);
}
3.4.3.1 類(lèi)圖
BaseStateManagerService 有一個(gè)類(lèi)型為Map的成員變量m_httpSessions,以Thread對象為key, HttpSession對象為值。HttpSession對象中屬性的key 是前面DefaultJetspeedRunData的StateEntry類(lèi)型的session數據操作接口的key,屬性的值為StateEntry 對象。StateEntry對象的成員變量m_key保存操作接口的key,成員變量m_map是一個(gè)Map對象,以后面我們要講的 setAttribute方法的name參數為 key,value參數為值。
3.4.3.2 初始化
下面的順序圖是一個(gè)簡(jiǎn)圖,主要用于解釋BaseStateManagerService的成員變量m_httpSessions的映射如何被填充和清除。
Turbine是一個(gè)servlet,其doGet方法是jetspeed系統的入口。
填充
Turbine 請求JetspeedRunDataService生成RunData對象,JetspeedRunDataService調用 HttpServiceRequest的getSession(true)方法獲取與當前請求對應的httpSession對象(以true為參數, getSession在當前session無(wú)效時(shí)會(huì )返回一個(gè)新的httpSession對象,否則返回先前請求的httpSession對象), JetspeedRunDataService接著(zhù)調用JetspeedHttpStateManagerService的 setCurrentContext(httpSession對象)方法,這個(gè)方法會(huì )以當前的Thread為key,參數httpSession對象為值填充BaseStateManagerService的成員變量m_httpSessions。
清除
doGet方法填充了m_httpSessions,并作了好多事情之后,在即將退出之前調用了JetspeedRunDataService的 putRunData(data)方法,這個(gè)方法再調用JetspeedHttpStateManagerService的 clearCurrentContext()方法刪除BaseStateManagerService的成員變量m_httpSessions中以當前 Thread為key的Map項。
下圖顯示了m_httpSessions對象經(jīng)過(guò)初始化后的內存狀態(tài)快照,體現了m_httpSessions對象保留的Thread-〉HttpSession的映射關(guān)系。
3.4.3.3 屬性操作
當DefaultJetspeedRunData通過(guò)session操作接口獲取SessionState之后,其它就可以使用SessionState對象的成員方法操作狀態(tài)屬性了。這兩個(gè)方法是:
public void setAttribute( String name, Object value );
public void removeAttribute( String name );
在從RunData對象處獲取sessionState對象后,jetspeed代碼可以調用這個(gè)對象的屬性操作方法。
setAttribute(name,value)操作的大概步驟是:
(1) 主要步驟:
1.1sessionState對象利用自己的key,結合參數name,value調用JetspeedHttpStateManagerService的setAttribute(key,name,value)方法;
1.1.1JetspeedHttpStateManagerService調用自己的getState(key)方法在參數key的幫助下獲取保存在當前線(xiàn)程session中的StateEntry對象的m_map變量,這個(gè)過(guò)程由1.1.1.1-1.1.1.4組成;
1.1.2得到StateEntry對象的m_map變量后,JetspeedHttpStateManagerService接著(zhù)先處理m_map中的先前的參數name對應的屬性值,再設置參數name對應的屬性值新值為參數value。
(2) 候選步驟:
1.1.1a 如果session中沒(méi)有相應的StateEntry對象,則先生成并往一個(gè)session中加入一個(gè)。
getAttribute(name)操作的大概步驟是:
(1) 主要步驟:
2.1sessionState對象利用自己的key,結合參數name調用JetspeedHttpStateManagerService的getAttribute(key,name)方法;
2.1.1JetspeedHttpStateManagerService調用自己的getState(key)方法在參數key的幫助下獲取保存在當前線(xiàn)程session中的StateEntry對象的m_map變量;
2.1.2得到StateEntry對象的m_map變量后,JetspeedHttpStateManagerService接著(zhù)調用m_map對象的get(name)方法獲取屬性值。
下圖體現了這些方法執行后HttpSession對象保留的key-> StateEntry對象以及StateEntry對象的Name->Value的映射關(guān)系。
3.5 修改建議
(1) 實(shí)現數據庫形式的repository。根據前面的集群需求第五條,必須把repository數據庫化才能使得集群下的各個(gè)jetspeed的資源視圖相同。
(2) StateEntry。根據前面的集群需求第一條,必須讓StateEntry實(shí)現Serializable接口。目前StateEntry是一個(gè)內部類(lèi),為了讓JVM的Serializer設施能順利創(chuàng )建StateEntry對象,最好把其public化。
(3) setAttribute要重設session屬性。根據前面的集群需求第二條,session對象的setAttribute是導致復制的引子,我們必須在改變session屬性后調用session對象的setAttribute方法重置session屬性,如下圖所示。
雖然Jetspeed中這樣模式的代碼如下:
更改JetspeedHttpStateManagerService的setAttribute方法。
對下面類(lèi)中的doXXX方法按照這個(gè)模式進(jìn)行修改。
controllers.MultiColumnControllerAction;
portlets.CustomizeSetAction;
controllers.RowColumnControllerAction;
注意StateEntry中的值的序列性。
4 總結
可以這樣說(shuō),目前的jetspeed在設計和實(shí)現時(shí)沒(méi)有考慮集群環(huán)境下的運行情況,本文的分析突出了jetspeed支持集群的主要癥結、但不一定完善,甚至有不正確的地方,另外一個(gè)主要內容是分析jetspeed保存在session對象中的數據。希望本文有助于大家加深對集群的理解,有助于提醒大家在設計和開(kāi)發(fā)軟件系統時(shí)"keep clustering in mind"。
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請
點(diǎn)擊舉報。