内存缓冲在大规模企业应用软件开发过程中是一个关键的技术,其中往往都有可伸缩性和高性能的需求。一个内存缓冲能存储应用程序状态信息(如,一个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的驱逐策略。
图2.传感器监控系统概述 |
图3.传感器监控系统的拓扑 |
图4.传感器监控系统的类图 |
/*** @@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(); ... } |
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(); } |
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 ……(篇幅所限省略) |