JBossCache 讲解说明
是什么?
一个树形结构、支持集群、支持事务的缓存技术。
有什么作用?
JBoss Cache是针对Java应用的企业级集群解决方案,其目的是通过缓存需要频繁访问的Java对象,提高应用的可用性并大幅度提升应用的整体性能。
JBoss Cache这样的分布式缓存扮演的是一个处于应用服务前端和数据库间的中间层的角色,提供对持久性数据状态在内存中的快速访问。JBoss Cache能够确保缓存中的数据状态和数据库中的状态一致、及时更新数据状态、并且保证JVM不会出现堆溢出问题。
为什么不使用Map?
很多人认为Map是考虑缓存的出发点(实际上,JSR-107 JCACHE专家组曾经在Map的基础上扩展实现javax.cache.Cache)。尽管Map非常适合用来存储简单的键/值对,在缓存必需的其它特性上,它就难免有点黔驴技穷,比如内存管理(eviction)、钝化(passivation)和持续性、细粒度锁定模型(首先,HashMap根本不是线程安全的;而ConcurrentHashMap采用的锁是粗粒度级的,它甚至不允许非阻塞用户或多用户从map中读取数据)等。而对于“合格的”缓存来说,它还需要具备一些“企业”特性,包括JTA兼容、附加侦听器等功能。
Map虽然是个好的起点,但如果需要实现或者管理我刚才提到的那些特性的话,选择缓存还是要比Map来得更合适一些。
JBossCache的缓存方式
JBoss Cache提供两种缓存方式:核心缓存(TreeCache)和POJO缓存(TreeCacheAOP)
核心缓存: 会直接把传递给它的数据存储在一个树型结构中。键/值对被存储在树的节点上,出于复制或持续性的需要它们都被序列化了 POJO 缓存: 则采用比较复杂的机制——利用字节码编织来内省(introspecting)用户类,并向用户类的域添加侦听器,一旦域值有任何变化,侦听器会立刻通知缓存。例如,如果要在POJO缓存中存储一个庞大、复杂的对象,会导致POJO缓存内省对象的字节码,最终只把该对象的原始域存储到树结构中。一旦域值有所变化,缓存只复制这个改变了的域值而不会去复制整个用户类,这是高效的细粒度复制。
在缓存面对庞大、复杂的对象的时候,细粒度复制确实有助于提高性能。但如果只是用它来存储一些String的话,细粒度复制就没有什么特别价值。类似地,对简单的用户对象运用POJO缓存——比方说一个只拥有两个String域的Person类,与其说对性能有什么帮助,倒不如说它是浪费开销。
下面主要针对核心缓存(TreeCache)进行讲解。
TreeCache的分类:
TreeCache按功能分为三类:本地(Local)Cache、复制(Replication)Cache和失效(Invalidation)Cache。 分布式Cache(复制和失效Cache)又分为两种,同步(REPL_ASYNC)和异步(REPL_SYNC),同步
Cache是在一个Cache实例做修改时,等待变化应用到其它实例后才返回,而异步Cache是在一个Cache实例做修改时,即刻返回。
缓存模式
LOCAL - 本地,非集群缓存。本地缓存不参与集群,也不同集群里的其他缓存通信。 因此,他 们的内容也不用串行化。但是,我们推荐将他们串行化, 这样将来的某一天想要改变缓存模式 时允许一定的灵活性。
REPL_SYNC - 同步复制。复制缓存将所有的变化复制到集群中的其他缓存。 同步复制意味着, 复制变化时,调用是阻塞的,直到收到复制确认。
REPL_ASYNC - 异步复制。与上面的 REPL_SYNC 类似, 复制缓存将所有的变化复制到集群中的 其他缓存。 因为异步,调用者不用阻塞直到收到复制确认。
INVALIDATION_SYNC - 如果缓存配置为失效而不是复制, 每次缓存里数据更新,集群里的其他 缓存将收到通知消息, 通知他们的数据已经陈旧了,应该从内存中驱逐。 这将减少复制的开 销,然而仍然能够使远程缓存中的陈旧数据失效。
INVALIDATION_ASYNC - 象上面一样,但是这个失效模式是用异步的方式广播失效消息的。
JBossCache 不得不说的那些机制 缓存加载器 缓存加载器是 JBoss Cache 的一个重要组成部分。 他们允许将节点持久化到磁盘或远程
缓存集群, 当缓存运行内存溢出时,允许钝化。 另外,缓存加载器允许 JBoss Cache 实现“温和启动”, 其中内存状态可以从持久存储中预先加载。 JBoss Cache 带有几个缓存加载器实现。
驱逐策略 驱逐策略通过管理有多少节点允许存储在内存中,以及他们的生命范围来控制着 JBoss
Cache的内存管理。 服务器上的内存限制决定了它不可能无限增长,因此需要有策略限制缓存的大小。驱逐策略绝大多数情况下与缓存加载器一同使用。配置文件中有相应的属性可供选择。
缓存钝化 当一个缓存驱逐数据时,可以用缓存加载器强制节点的钝化和激活。缓存钝化是当驱逐时,
从内存缓存移除一个对象并将它写入二级数据存储 (例如,文件系统、数据库)的过程。 缓存激活是当需要使用时,将一个对象从数据存储中恢复到内存缓存的过程。在这两种情况下,用配置的缓存加载器读出和写入数据存储。
并发
JBoss Cache 是个线程安全的缓存接口,并且使用它自己控制并发的有效机制。 为了实现树结构中的每个节点对应一个锁。这些锁的隔离级别和数据库实施的隔离级别相同,允许多用户同时读取数据。
高效的目的,缺省情况下它使用悲观锁方案。
事务 通过配置,JBoss Cache 可以使用并参与 JTA 兼容的事务。
缓存中的数据结构:
一个 Cache 由一组 Node 的实例组成, 这些 Node 按树型结构组织起来。 每个 Node 包含一个用于缓存数据对象的 Map。 需要重点关注的是,这个结构是一个数学树而不是图; 每个
Node有且仅有一个父节点, 而根节点则固定不变地被命名为 Fqn.ROOT。像这样组织节点的原
因是,可以提高数据的并发访问,并能在更细粒度上实现复制和持久化。
由于缓存本质上就是节点的集合,因此对于整个缓存或其中的单个节点的调用, 就集中在
对节点的集群、持久化、驱逐等方面。
关于Fqn的使用说明:
Fqn 是Fully qualified name(完全限定名)的缩写。 1. Set up the Fqns you need. create 3 Fqn variables:
childFqn1 = Fqn.fromString("/child1"); childFqn2 = Fqn.fromString("/child2"); childFqn3 = Fqn.fromString("/child2/child3");
2. Create child nodes under the root node.
child1 = root.addChild(childFqn1); child2 = root.addChild(childFqn2); child3 = root.addChild(childFqn3);
3. Query the nodes.
root.hasChild(childFqn1); // should return true
child2.hasChild(childFqn3.getLastElement()); // should return true child3.getParent(); // should return child2 child2.getParent(); // should return roo
4. Put some data in the nodes. By selecting the nodes in the tree view, you should see the contents of each node.
child1.put("key1", "value1"); child1.put("key2", "value2"); child2.put("key3", "value3"); child2.put("key4", "value4"); child3.put("key5", "value5"); child3.put("key6", "value6");
5. Query some of the data.
child1.getKeys(); child2.getData();
6. Remove some data in the nodes.
child1.remove("key1"); child2.remove("key3"); child3.clearData();
7. Delete nodes
root.removeChild(childFqn1); // will also remove any data held under child1 root.removeChild(childFqn2); // will recursively remove child3 as well.
JBossCache环境搭建与配置:
在 AS 5 的 server/all/ 中,有着关于集群及JBossCache 缓存配置的全部相关文件。
若想要准备多个环境,可以拷贝多份,并重新命名。配置文件则不需要另外准备,目录中有默认的设置。
部署JBoss Cache 及 操作 JBoss Cache
通过 CacheManager 服务部署。
在将你的缓存配置加到CacheManager之后,下一步则是为你的应用提供一个CacheManager有三种方式可以达到这个目的: 1、 依赖注入
前提是你的应用使用了JBoss Microcontainer 去配置 2、 JNDI查找
它绑定在java:CacheManager下 3、 CacheManagerLocator
的引用。
一旦获取到CacheManager的引用,使用起来则十分的简便。访问一个缓存通过传递一个你渴望的配置名。
配置名信息可以在JBOSS_HOME/server/all/deploy/cluster/jbosscache-manager.sar. 中
找到。其中有对应的缓存配置的详细信息。
已“JNDI 查找”方式为例:
Context ctx = new InitialContext();
CacheManager cacheManager = (CacheManager) ctx.lookup("java:CacheManager");
cacheManager.start();
cache = cacheManager.getCache("mvcc-entity", true); cache.start();
if(cache.getCacheStatus().allowInvocations() ) { }
Node rootNode = cache.getRoot();
Fqn peterGriffinFqn = Fqn.fromString("/griffin/peter"); Node peterGriffin = rootNode.addChild(peterGriffinFqn); peterGriffin.put("isCartoonCharacter", Boolean.TRUE);
cache.start();
如此操作JBossCache.。
在实际工作中的应用
看了上面的说明,是不是对Jboss Cache有了一定的了解了哪?
在数据库缓存的实际应用中,Jboss Cache一般用来缓存两种类型的数据,一种是频繁变化的数据,比如说证券业的行情,1秒钟更新一次,要读取N多次,放到数据库中根本不可行。
一种是不太变化的数据,比说如用户,组织,权限的数据,不会经常变化,但是会经常check用户的权限,这类表也适合放入到缓存中保存。
2 JbossCache的用法
1 下载JbossCache的包
2 配置JbossCache.xml文件,例子如下:
<?xml version="1.0" encoding="UTF-8"?>
<jbosscache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:jboss:jbosscache-core:config:3.0">
<!--
isolation levels supported: READ_COMMITTED and REPEATABLE_READ
nodeLockingSchemes: mvcc, pessimistic (deprecated), optimistic (deprecated)
-->
<locking
isolationLevel="REPEATABLE_READ"
lockParentForChildInsertRemove="false"
lockAcquisitionTimeout="20000"
nodeLockingScheme="mvcc"
writeSkewCheck="false"
concurrencyLevel="500"/>
<!--
Used to register a transaction manager and participate in ongoing transactions.
-->
<!-- <transaction
transactionManagerLookupClass="org.jboss.cache.transaction.GenericTransactionManagerLookup"
syncRollbackPhase="false"
syncCommitPhase="false"/>
-->
<!--
Used to register JMX statistics in any available MBean server
-->
<jmxStatistics
enabled="false"/>
<!--
If region based marshalling is used, defines whether new regions are inactive on startup.
-->
<startup
regionsInactiveOnStartup="true"/>
<!--
Used to register JVM shutdown hooks.
hookBehavior: DEFAULT, REGISTER, DONT_REGISTER
-->
<shutdown
hookBehavior="DEFAULT"/>
<!--
Used to define async listener notification thread pool size
-->
<listeners
asyncPoolSize="1"
asyncQueueSize="1000000"/>
<!--
Used to enable invocation batching and allow the use of Cache.startBatch()/endBatch() methods.
-->
<invocationBatching
enabled="true"/>
</jbosscache>
3 在启动时加载JbossCache,可以放到Listener中启动,示例程序如下:
/**
*loadtheConsttabletomemorywhenappserverstart
*/
privatevoid initConstTableToMemory() {
// init the jboss cache
CacheFactory factory = new DefaultCacheFactory();
Cache cache = factory.createCache("JBossCache.xml");
cache.create();
cache.start();
WebContextHolder.getInstence().setJbossCache(cache);
RefreshConstToMemory memory = new RefreshConstToMemory();
memory. refreshPdmmthdToMemory ();
}
4 RefreshConstToMemory是存放Jboss Cache数据的例子:
publicvoid refreshPdmmthdToMemory() {
Cache cache = WebContextHolder.getInstence().getJbossCache();
Node rootNode = cache.getRoot();
Node node = null;
Fqn fqn = null;
//从数据库中取出pdmmthd表的内容,放到Jboss Cache中。
PdmmthdDAO pdmmthdDAO = (PdmmthdDAO) BeanUtil.getBean("pdmmthdDAO");
List<Pdmmthd> pdmmthdList = pdmmthdDAO.getAllMethodFromDatabase();
node = cache.getNode("/pdmmthd");
if (node == null) {
fqn = Fqn.fromString("/pdmmthd");
node = rootNode.addChild(fqn);
}
node.put("/pdmmthd", pdmmthdList);
for (Pdmmthd pdmmthd : pdmmthdList) {
node.put(pdmmthd.getMtcode(), pdmmthd);
}
}
在数据库第一次启动,在数据更新以后,都需要手工调用上述的refreshALLMemory()方法。
为了保险起见,对于不支持Jboss Cache的情况,或者Jboss Cache出现故障,我们可以在查询时增加出错处理,示例程序如下:
public List<Pdmmthd> getAllMethodFromMemory() {
// try the Exception in case of can't find 'method' in memory
try {
Cache cache = WebContextHolder.getInstence().getJbossCache();
Node node = cache.getNode("/pdmmthd");
List list = (List) node.get("/pdmmthd");
// return a new List,cause the user need change the list.
// For example,It will add "All" method to the list.
List dest = new ArrayList();
dest.addAll(list);
return dest;
} catch (Exception e) {
return getAllMethodFromDatabase();
}
}