top
Loading...
把JBoss緩存用作POJO緩存的實戰演練
一、 引言

內存緩沖在大規模企業應用軟件開發過程中是一個關鍵的技術,其中往往都有可伸縮性和高性能的需求。一個內存緩沖能存儲應用程序狀態信息(如,一個WEB應用程序中的HttpSession)或數據庫查詢結果(也即,實體數據)。由于許多企業應用軟件運行在一個簇環境下,所以緩存需要跨越簇進行復制。而且,如果需要更高的可靠性的話,內存緩沖也應該被持續性存儲到硬盤或數據庫中去。

大多數內存緩沖解決方案都屬于我們所稱的"普通"緩存系統類——其中存儲和緩沖直接參考的對象。既然一個普通緩存直接處理參考對象,那么它就象一個詳盡的HashMap結構一樣,并因此使用起來非常直觀。當一對象需要被復制或持續存儲到一個普通緩存系統中時,對象必須實現Serializable接口。然而,普通緩存在復制或持續存儲方面也存在一些明顯的限制:

·用戶必須具體地管理該緩存。例如,當一對象被更新時,用戶需要執行一相應的API來更新緩存內容。

·Java對象串行化的需要可能會對性能有所妨礙。如果對象是巨大的,甚至單個的字段更新也將會激活整個對象的串行化與跨整個簇的復制。這樣可能帶來不必要的昂貴的代價。

·Java對象串行化不可能保存緩沖對象之間的關系。特別地,該緩沖對象不可能被其它對象參考多次(多參考),或到其自身有一個間接參考(循環)。否則,在串行化時該關系將被打破。例如,圖1說明在復制期間的這個問題。如果我們有兩個共享同一個Address對象的Person實例,那么在復制時它將被拆分成兩個獨立的Address實例(而不是一個)。


圖1.普通緩存在串行化期間不保留對象關系

相對于上面普通緩沖系統中存在的問題,還有另外一種新型的緩沖系統——POJO(簡單Java對象)緩存。一個POJO緩存是一個系統——它擔當一個"面向對象的"分布式的緩存。在這個系統中,一旦一個用戶把POJO依附到該緩存上,那么緩沖方面(例如復制和持續性)應該對用戶是透明的。一個用戶只需簡單地在該POJO上操作而不須擔心更新該緩存內容或維持對象關系的問題。不存在顯式的API調用可用來管理該緩存。另外,它還有三個特征:

·不需要為POJO實現Serializable接口。

·復制(或甚至持續性)是基于字段級的(這與普通緩存中的基于整個對象的二進制級形成對照)-這將導致潛在的性能推進。

·對象關系和身份被自動地保存在一分布式復制環境中,這帶來透明的使用體驗并且提高了軟件性能。

一個領先的內存POJO緩存解決方案就是開源JBoss緩存。JBoss緩存是第一個Java庫——它支持可復制的,持續性的,事務性的和良好粒度的緩沖,它可以被使用作為POJO緩存和普通緩存。既然JBoss緩存是百分之百基于Java的,那么它就可以運行在任何Java SE環境中——包括應用程序服務器內部或作為獨立的進程。例如,JBoss緩存已經被應用到針對EJB 3.0有狀態的會話bean簇和HPP會話復制的應用程序服務器中。

在本文中,我們將說明怎樣把JBoss緩存用作一個POJO緩存(通過它的JBossCacheAop組件)。同時,還將給出一個應用案例來說明在分布環境中的一些關鍵特性。

二、 JBoss緩存概述

(一) 普通緩存

JBoss緩存中默認的普通緩存模塊稱為TreeCache。你可以通過編程方式或通過一外部XML文件對它進行設置。下面是你可以設置的一些特性:

1. 緩存模式:它可以是本地的或者是可復制的。如果它是可復制的,那么你可以進一步指定同步的或異步的模式。

2. TransactionManager:你可以為JBoss緩存查詢指定一個與JTA相匹配的事務管理器。如果它發現一個正在進行中的事務上下文,那么它就會參予到該事務中并且相應地執行提交或回滾。

3. 可插入的驅逐策略:該緩存驅逐策略參考該緩存使用的算法來終止它的內容。在JBoss緩存中,你可以經由一個可插入的接口來實現你自己的驅逐策略。JBoss緩存當前與一個基于地域化的LRUEvictionPolicy一起發行。

4. 隔離級別:JBoss緩存使用JDBC風格的語義來實現鎖定行為。你不是專門地指定讀/寫鎖,而是為了易于使用起見,可以指定不同的隔離級別。

5. 可插入的CacheLoader:CacheLoader允許你把持續性緩存內容裝載回內存中。JBoss緩存當前支持文件裝載器和基于SleepyCat和JDBC的裝載器。

6. 溢出:通過與一個緩存裝載器和一個驅逐策略相聯合,它提供了可以在EJB中見到的鈍化/激活特性。無論什么時候驅逐一項,它都將被鈍化-這樣它就會一直是持續的。

(二) POJO緩存

JBoss緩存中的POJO緩存模塊稱作TreeCacheAop。為了使用該POJO緩存,你必須在這些對象被緩沖以前"準備"這些對象(這個過程也稱作對象運行時字節碼重構)。系統在攔截該POJO操作時正需要這樣做。對象運行時字節碼重構過程由JBoss AOP庫來實現。JBoss AOP允許你經由一個XML文件或注解來指定將被進行字節碼重構重構的類。當前,我們僅僅支持JDK-1.4風格的注解(一個特定的JBoss AOP特征)。JDK 5.0注解支持將要在下一個版本中才能出現并且它將使得運行時字節碼重構過程幾乎是透明的!

TreeCacheAop是一個TreeCache的子類,因此它使用相同的XML文件進行配置并且提供與它的超類部分相同的緩沖功能。該JBoss POJO緩存還提供基于POJO的驅逐策略。

三、 應用案例分析

在本文的后面,我們將使用一個"傳感器網絡監控系統"示例來說明JBoss POJO緩存在提供即時的良好粒度的狀態復制和自動保存狀態對象關系方面的能力。

(一) 問題描述

沿著一條高速鐵路有一些不同的站點,這里有幾千個傳感器需要被監視和監控。這些儀器的一些示例包括溫度,風和雨傳感器-它們對于高速鐵路的正常運轉至關重要。如果一個特別的傳感器正在失靈,那么在監督的系統中的管理器計算機應該向超級用戶警報并且關掉該單元并/或調度它-為維護之目的。

既然該操作是任務第一性的,那么就要求監督系統必須是高度可用的-這意味著無論何時在系統中的一臺傳感器管理器計算機出了問題,超級用戶都應該能夠無縫地切換到另一臺機器以實現監視和積極的管理。因此,所有的管理器計算機必須實時地復制相同的傳感器網絡狀態信息。注意,這種類型系統的特征-也就是,高可用性的要求和幾千個(甚至更大數字)元素的存在-在現代網絡管理中在其它一些情況下也是普通存在的。圖2說明如此一個系統的概述-其中包含了簇能力。


圖2.傳感器監控系統概述

因為傳感器網絡的層次特性,典型情況下,在管理器方面一般都需要用一個復雜的域對象圖來建模該傳感器網絡。當域對象狀態沒有被復制(或持續性存儲時),對象關系的管理(例如,添加結點和遍歷圖添加)將由JVM本身來提供,并因此而對終端用戶透明。然而,因為Java串行化過程不能夠識別對象關系,所以當狀態是復制或持續性時,這個看上去相當簡單的對象-圖關系將會垮掉。結果是,它導致管理器端組件的一個簡單的持續性問題都難于實現。

傳統地,為了提供完全的持續性能力,必須設計專門的系統來顯式地管理對象關系,就象在一個現代對象關系映射(ORM)的解決方案一樣。并且在一個傳統型實體持續性層風格設計中,你必須做到:

· 外在化關系映射(例如,在一個XML文件中)。

· 把對象分解成一些原型。

· 建立對象身份的概念以保留關系。

· 追蹤使用中的字段(如果你想要避免整個的對串行化的代價)。

· 最后,把原始類型裝配成對象。

然而,通過使用JBoss POJO緩存來管理你的對象狀態,根本不存在上面提及的問題!說得更詳細些,好處如下:

· 即時持續性(通過狀態復制)-不修改你的域對象模型或指定復雜的對象關系映射。

· 可選的狀態持續性也能夠保存對象圖。

· 可以批量實現良好粒度的復制(或持續性)-為優化網絡交通。

如我們在上面描述的,在POJO緩存中的復制和/或持續性方面對用戶是完全透明的。注意,對整個簇來說,還存在另一個方面-裝載平衡或定位主和/或輔管理器-客戶端GUI(對超級用戶來說)和傳感器都應該連接到其上。在此我們不討論這些問題,而將只集中于討論跨管理器的傳感器網絡狀態的復制問題。

(二) 拓撲與對象建模

圖3是我們的傳感器監控系統示例的拓撲結構。基本上,在這個簡化了的實例中,我們有兩個站(Tokyo和Yokohama),而且在每一個站中,我們將有一個Wind和一個Rain傳感器。對于每一種傳感器,存在許多組件需要監控-例如,電源供應和傳感器單元本身。我們的目標是高效地監控傳感器-這是通過1)監視單個項的狀態(例如,一個StateItem)以及它們的整個的狀態(例如,Wind Summary);2)能夠在運行時刻調入和調出單個傳感器而不用重啟簇管理器結點。


圖3.傳感器監控系統的拓撲

圖4展示了使用一個類圖的這個域模型。實質上,我們在最頂層有一個PropagationManager-它管理整個傳感器網絡。它總是有一個根Node。該根Node可能遞歸地擁有眾多的Node。每一個Node可以有多個StateItems。一個StateItem是最小的管理單元-例如,描述該單元的電源供給。


圖4.傳感器監控系統的類圖

對于圖3中的當前拓撲來說,Japan代表根結點,而Tokyo和Yokohama分別為站結點。不同的傳感器(Rain或Wind)各用一個Node對象來描述。然后,每個傳感器有兩個StateItems,也就是Power Supply和Sensor Unit。

這些結點之間的關系是雙向的;我們可以從根結點一直導航到葉結點,或者我們也可以經由父結點往回導航。另外,還有一個WindSensor Summary和一個RainSensor Summary StateItem實例-它們參考各自的傳感器。Summary項的目的是監控整個的Wind和Rain傳感器單元的健康狀態。結果是,該傳感器的對象圖中的對象是多參考的(顯示于圖2和3中)。

(三) 配置

在我們可以使用POJO緩存功能之前,我們需要使用JBoss AOP工具來重構這些POJO(也就是,PropagationManager,Node和StateItem類)。為此,我們可以經由XML聲明或注解來實現。下面我們將展示經由JBoss AOP JDK 1.4注解(JDK 5.0注解將會在下一個JBoss緩存發行版本中得到支持)對POJO進行字節碼重構。下面是針對三個主要接口的聲明注解的代碼片斷:

/*** @@org.jboss.cache.aop.InstanceOfAopMarker*/
public interface PropagationManager {
public void setRootNode(String rdn);
public void addNode(String parentFdn, String rdn);
public void addStateItem(String parentFdn,long itemId, String name, long defaultState);
public Node findNode(String fdn);
public void stateChange(String fdn, long itemId,long newState);
...
}
/*** @@org.jboss.cache.aop.InstanceOfAopMarker*/
public interface Node {
public void addChildNode(Node child);
public List getChildren();
public void setParentNode(Node parent);
public Node getParentNode();
public void addStateItem(StateItem stateItem);
public void setSummaryStateItem(StateItem stateItem);
public StateItem getSummaryStateItem();
public List getStateItems();
public StateItem findStateItem(long itemId);
public void setPropagationRule(PropagationRule rule);
...
}
/*** @@org.jboss.cache.aop.InstanceOfAopMarker*/
public interface StateItem {
public long getItemId();
public boolean setState(long state);
public long getState();
public void setName(String name);
public String getName();
...
}

注意,在該JavaDoc中的注解-@@org.jboss.cache.aop.InstanceOfAopMarker是一個JBoss POJO緩存注解-它實質上聲明這個接口的所有實例都將被進行字節碼重構,因此不需要注解單個類。如果你想要注解一特定的類(而不涉及到它的子類),你也可以使用@@org.jboss.cache.aop.AopMarker注解。

在注解接口之后,我們使用一個JDK-1.4風格的JBoss AOP注解預編譯器-annoc和一個AOP預編譯器-aopc來執行編譯時刻字節碼重構。一旦完成這些步驟也就完成了字節碼重構過程,然后我們就可以開始運行這個示例程序了。

(四) 代碼片斷

下面是代碼片段-它實例化一個PropagationManager實例并設置站點和傳感器結點之間的正確關系。最后,我們使用TreeCacheAop API putObject()來把POJO(在這種情況中,是PropagationManager實例)放入緩存管理之中。之后,任何POJO操作都將被良好地粒度地復制;例如,一個setState()操作將只復制相應的狀態字段(它是一個整數)。

protected void setUp() throws Exception {
cache1_ = createCache("TestCluster");
cache2_ = createCache("TestCluster");
initPm();
}
protected void tearDown() throws Exception {
cache1_.remove("/");
cache1_.stop();
cache2_.stop();
}
private TreeCacheAop createCache(String name) throws
Exception {
//通過注入來配置緩存
PropertyConfigurator config = new PropertyConfigurator();
//讀取replSync xml.
//這里我們使用同步復制模式.
config.configure(tree, "META-INF/replSync-service.xml");
//我們可以設置一個不同的簇組.
tree.setClusterName(name);
tree.start(); //開始緩存
return tree;
}
/***填充繁殖樹*/
protected void initPm() throws Exception {
pm_ = new PropagationManagerImpl();
pm_.setRootNode("Japan");
pm_.addNode("Japan", "Tokyo"); //東京站
// Wind傳感器設備
pm_.addNode("Japan.Tokyo", "WindSensor1");
pm_.addStateItem("Japan.Tokyo.WindSensor1", 1000,"power supply", 1040); //電源供應
pm_.addStateItem("Japan.Tokyo.WindSensor1", 1001, "sensor unit", 1040); //傳感器單位
//Rain傳感器設備
pm_.addNode("Japan.Tokyo", "RainSensor1");
pm_.addStateItem("Japan.Tokyo.RainSensor1", 1002,"power supply", 1040); //電源供應
pm_.addStateItem("Japan.Tokyo.RainSensor1", 1003,"sensor unit", 1040); //傳感器單位
pm_.addNode("Japan", "Yokohama"); // 橫濱站
//Wind傳感器設備
pm_.addNode("Japan.Yokohama", "WindSensor2");
pm_.addStateItem("Japan.Yokohama.WindSensor2", 1000,"power supply", 1040); //電源供應
pm_.addStateItem("Japan.Yokohama.WindSensor2", 1001,"sensor unit", 1040); //傳感器單位
// Rain傳感器設備
pm_.addNode("Japan.Yokohama", "RainSensor2");
pm_.addStateItem("Japan.Yokohama.RainSensor2", 1002,"power supply", 1040); //電源供應
pm_.addStateItem("Japan.Yokohama.RainSensor2", 1003, "sensor unit", 1040);//傳感器單位
//網絡中的Wind傳感器的摘要結點
pm_.createNode("WindSummary", "WindSummary");
pm_.setUpperNode("WindSummary","Japan.Tokyo.WindSensor1"); //關聯
pm_.setUpperNode("WindSummary","Japan.Yokohama.WindSensor2"); //關聯
//網絡中的Rain傳感器的摘要結點
pm_.createNode("RainSummary", "RainSummary");
pm_.setUpperNode("RainSummary", "Japan.Tokyo.RainSensor1"); //關聯
pm_.setUpperNode("RainSummary","Japan.Yokohama.RainSensor2"); //關聯
}
/**主啟動點,由main來調用*/
public void testPropagation() throws Exception {
//在此我們讓POJO緩存來管理pm
cache1_.putObject("/monitor", pm_);
//輸出
printStatus("Initial state", pm_);
// 從Manager #2檢索POJO
PropagationManager pm2 = (PropagationManager) cache2_.getObject("monitor");
System.out.println( "---------------------------------------------");
System.out.println("Modified on Manager #1");
//該項中的一個狀態被修改.
//這將是良好粒度的復制
pm_.stateChange("Japan.Tokyo.RainSensor1", 1003, 1041);
printStatus("Japan.Tokyo.RainSensor1: id: 1003 state:
1040->1041 (retrieved from Manager #2!)", pm2);
System.out.println( "---------------------------------------------");
System.out.println("Modified on Manager #2");
//該項中的一個狀態被修改.
//這將是良好粒度的復制.
pm2.stateChange("Japan.Yokohama.WindSensor2",
1001, 1041); //在緩存#2上修改狀態
printStatus("Japan.Yokohama.WindSensor2: id: 1001 state:
1040->1041 (retrieved from Manager #1!)", pm1);
System.out.println( "---------------------------------------------");
System.out.println( "Add a new VibrationSensor on Tokyo station");
// Vibration傳感器設備
pm_.addNode("Japan.Tokyo", "VibrationSensor1");
pm_.addStateItem("Japan.Tokyo.VibrationSensor1", 1004,"power supply", 1040); // 電源供應
pm_.addStateItem("Japan.Tokyo.VibrationSensor1", 1005,"sensor unit", 1040); //傳感器單位
printStatus("Japan.Tokyo.VibrationSensor1: (retrieved from cache #2)", pm2);
}
public static void main(String[] args) throws Exception {
PropagationReplAopTest pmTest =new PropagationReplAopTest();
pmTest.setUp();
pmTest.testPropagation();
pmTest.tearDown();
}

在上面的實例片斷中,請注意下列幾點:

1.兩個緩存實例被通過XML注入配置成功并且在隨后啟動。它們可以運行在兩個獨立的JVM中(并且在不同的機器上)。

2.一個PropagationManager的實例經由一個putObject() API調用被放入到緩存管理中。此后,只需要單純的POJO操作。例如,我們可以把額外的結點添加到PropagationManager實例上并且它將會相應地進行復制。

3.經由一個getObject()調用,我們在緩存#2上檢索PropagationManager的一個新實例。

4. 各種setState()調用只會激活到其它的緩存實例的良好粒度的復制。

(五) 結果輸出

最后,當運行這個實例時,結果的輸出如下所示:

Initial state
---------------------------------------------
Japan (Summary : 2000 [ok])
+ Tokyo (Summary : 2000 [ok])
+ + WindSensor1 (Summary : 2000 [ok])
+ + | ( name = power supply, id = 1000, state =1040)
+ + | ( name = sensor unit, id = 1001, state =1040)
+ + RainSensor1 (Summary : 2000 [ok])
+ + | ( name = power supply, id = 1002, state =1040)
+ + | ( name = sensor unit, id = 1003, state =1040)
+ Yokohama (Summary : 2000 [ok])
+ + WindSensor2 (Summary : 2000 [ok])
+ + | ( name = power supply, id = 1000, state =1040)
+ + | ( name = sensor unit, id = 1001, state =1040)
+ + RainSensor2 (Summary : 2000 [ok])
+ + | ( name = power supply, id = 1002, state =1040)
+ + | ( name = sensor unit, id = 1003, state =1040)
---------------------------------------------
Modified on Manager #1
StateItem.setState(): id: 1003 state changed
from 1040 to 1041
……(篇幅所限省略)

請注意其中加粗的幾行。基本上,我們首先在管理器#1上進行了一個POJO操作setState,然后打印輸出propagation(繁殖)樹來驗證狀態被更新了,并且反過來也如此。如此重復是值得的,盡管沒有顯示在復制層數據流的輸出結果中,但是每一個setState()操作也將會只激活具有良好粒度的字段級復制。另外,如果該調用是在一個事務上下文中,那么更新將會被批量地進行;也就是說,只要準備好提交時才進行復制。最后,還要注意,我們已經能夠輕松地把一個新的傳感器添加到網絡中。一個傳統型的系統一般都要求某種類型的重啟機制。

四、 總結

在本文中,我們分析了通過平衡TreeCacheAop組件來實現用JBoss緩存擔任POJO緩存的能力。通過使用POJO緩存功能,它能夠為POJO提供無縫的持續性存儲的能力-在保留對象-圖關系的同時又能實現良好粒度的復制。
作者:http://www.zhujiangroad.com
來源:http://www.zhujiangroad.com
北斗有巢氏 有巢氏北斗