Jive学习(一)

一、缓存机制的原理:在数据从持久层取出来后保存在内存中,下次读取的时候,直接从内存中读取。

 

二、缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用的运行性能。缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据
  缓存的介质一般是内存,所以读写速度很快。但如果缓存中存放的数据量非常大时,也会用硬盘作为缓存介质。缓存的实现不仅仅要考虑存储的介质,还要考虑到管理缓存的并发访问和缓存数据的生命周期。(2006-03-16 08:13作者:lisign出处:jdon)

 

三、 jive缓存帖子的实现:http://www.javaresearch.org/article/28609.htm

    在Jive 2.1.2中,涉及Jive缓存机制的Java类大致可以分为以下四个部分(为了简化起见,本文只讨论帖子缓存机制的实现。用户名和权限的存取虽然也用到了缓存,但其实现机制与前者类似,因此不再赘述):
  第一部分,提供HashMap、LinkedListedlist等数据结构,以便实现缓存机制,其中HashMap是JDK提供的,其Key类型为Object。可以在com.jivesoftware.util包中找到这些数据结构。此部分包括Cache类、 LinkedList类、LinkedListNode类、Casheable接口、CacheObject类、CacheableBoolean类、CacheableInt类、CacheableLong类、CacheableLongArray类、CacheableString类、CacheSizes类、CacheTimer类。
  第二部分,提供LongHashMap、LongLinkedListedlist等数据结构以实现缓存机制。与第一部分不同的是,它的HashMap是自己编写的,其Key为Long型,因此被冠以LongHashMap的名称。同样可以在com.jivesoftware.util包中找到它们。该部分包括LongHashMap类、LongCache类、 LongCacheObject类、LongLinkedList类和LongLinkedListNode类。还有第一部分中的Casheable接口,它的各种数据类型的实现、CacheSizes类和CacheTimer类,也可归于这部分。它们可看作是第一部分和第二部分的交集。
  第三部分,调用底层数据结构以提供论坛对象的缓存。可以在com.jivesoftware.forum.database包中找到这些底层数据结构。该部分包括的类主要有DatabaseCacheManager类、DbForumFactory类、DbForum类、DbForumThread类、DbForumMessage 类、DatabaseCache类、ForumCache类、 ForumThreadCache类和ForumMessageCache类;
  第四部分,向Jsp页面提供访问接口,同样可以在com.jivesoftware.forum.database包中找到这些接口。该部分包括的类有ForumThreadBlockIterator类和ForumMessageBlockIterator类,第三部分的DbForum类、DbForumThread类和DbForumMessage 类也可以包括进来。实际上,这三个类是第三部分和第四部分联系的纽带。在com.jivesoftware.util包中还有一个LongList类,它用来将ForumThreadBlockIterator类和ForumMessageBlockIterator类转化成Long型数组,因此也应算在这部分。
  从上面介绍可看出,缓存机制也可以划分为三层,即第一和第二部分的底层数据结构,第三部分的中间层和第四部分的上层访问接口,下面分别讨论它们。

(一)底层数据结构
  Jive缓存机制的原理其实很简单,就是把所要缓存的对象加到HashMap哈希映射表中,用两个LinkedListedlist双向链表分别维持着缓存对象和每个缓存对象的生命周期。如果一个缓存对象被访问到,那么就把它放到链表的最前面,然后不定时地把要缓存的对象加入链表中,把过期对象删除,如此反复。实际上比较第一和第二部分就可以发现,它们的代码几乎完全相同。差别就在第二部分的哈希映射表没有采用JDK提供的类,而是采用了作者自己编写的一个类,将原来哈希映射表的Key类型由Object改为Long。这样做虽然在一定程度上加快了缓存的速度,并减小了缓存的大小,但无形之中也减低了程序的稳定性和可读性,因此不推荐仿效。值得一提的是,在Jive 1.0.2版中,所有Forum、Thread、Message的ID和它们内容的缓存都是用第一部分的Java类实现的。它在升级到后面的版本时,其内容采用了第二部分的Java类实现,但其ID仍用第一部分的Java类实现,这是Jive中值得注意的一个地方。下面先来看第一部分的Java类实现。LinkedListNode类的源码为:

public class LinkedListNode {
 public LinkedListNode previous;
 public LinkedListNode next;
 public Object object;
 public long timestamp;

 public LinkedListNode(Object object, 
   LinkedListNode next,LinkedListNode previous){
  this.object = object;
  this.next = next;
  this.previous = previous;
 }
 public void remove() {
  previous.next = next;
  next.previous = previous;
 }
 public String toString() {
  return object.toString();
 }
}

很明显,这是一个双向链表的节点类,previous、next分别记录前后节点的指针,object用于记录所需缓存的对象,timestamp用于记录当前节点被创建时的时间戳。当该时间戳超过该节点的生存周期时,它就会被remove()方法删除掉。该类主要完成的功能就是由LinkedListNode构成LinkedList链表,而由LinkedList类实现getFirst()、getLast()、addFirst()、addLast()、clear()等链表的基本方法。


  再来看Cacheable接口和它的一个实现类CacheableInt的源码:

public interface Cacheable {
 public int getSize();
}
public class CacheableInt implements Cacheable {
 private int intValue;
 public CacheableInt(int intValue) {
  this.intValue = intValue;
 }
 public int getInt() {
  return intValue;
 }
 public int getSize() {
  return CacheSizes.sizeOfObject() + CacheSizes.sizeOfInt();
 }
}

从上面的代码可以看到,Cacheable接口只有一个方法getSize()。它要求所有继承类实现该方法,并输出占用缓存的大小,以便实施管理。那么为什么CacheableInt. getSize()得到的是sizeOfObject()+sizeOfInt()呢?因为任何类都继承自Object,计算空间时当然也要把它算上了。


  还有一个CacheObject类,它是缓存的基本元素,来看一下它的代码:

public final class CacheObject {
 public Cacheable object;
 public int size;
 public LinkedListNode lastAccessedListNode;
 public LinkedListNode ageListNode;

 public CacheObject(Cacheable object, int size) {
  this.object = object;
  this.size = size;
 }
}

lastAccessedListNode记录着一个缓存节点的Key值,是构成lastAccessedList链表的基本元素,在lastAccessedList链表中,经常被访问到的节点总是在最前面。ageListNode记录着缓存节点的加入时间,是构成ageList链表的基本元素。而ageList链表是按时间先后排序,先加入的节点总是在最后面。lastAccessedListNode和ageListNode本来可以分写成两个类,毕竟lastAccessedListNode并不需要ageListNode的成员变量timestamp,但是为了简化程序,Jive把它们写成了一个类。这也是值得注意的一个地方。


  现在来看缓存机制中最关键的一个类Cache的部分代码,其中主要是add()和get()方法。有关这两个方法的介绍请参考代码中的注释。

public class Cache implements Cacheable {
 protected static long currentTime = CacheTimer. currentTime;
 protected HashMap cachedObjectsHash;
 protected LinkedList lastAccessedList;
 protected LinkedList ageList;
 //缓存元素的最大尺寸128KB,可修改
 protected int maxSize =  128 * 1024; 
 //整个缓存的大小
 protected int size = 0;
 //缓存元素的最大保存时间,用Cache(long maxLifetime)初始化
 protected long maxLifetime = -1;
 //记录cache的命中次数和未命中次数
 protected long cacheHits, cacheMisses = 0L; 
 ......
 //向哈希表中添加一个关键字为Key的缓存对象object
 public synchronized void add(Object key, Cacheable object) {
  //先把原来的对象remove掉
  remove(key);
  int objectSize = object.getSize();
  //如果对象太大,则不加入缓存
  if (objectSize > maxSize * .90) {
   return;
  }
  size += objectSize;
  //新建一个缓存对象,并放入哈希表中
  CacheObject cacheObject = new CacheObject(object, objectSize);
  cachedObjectsHash.put(key, cacheObject);
  // 把缓存元素的Key放到lastAccessed List链表的最前面
  LinkedListNode lastAccessedNode = lastAccessedList.addFirst(key);
  cacheObject.lastAccessedListNode = lastAccessedNode;
  //把缓存元素的Key放到ageList链表的最前面,并记下当前时间
  LinkedListNode ageNode = ageList.addFirst(key);
  ageNode.timestamp = System.currentTimeMillis();
  cacheObject.ageListNode = ageNode;
  // 在cullCache()中,先调用deleteExpiredEntries()把过期对象删掉,
  如果缓存还是太满,则调用  remove(lastAccessedList.getLast().object)把
 //  lastAccessedList中不常访问的对象删掉
  cullCache();
 }
 //在哈希表中得到一个关键字为Key的缓存对象object
 public synchronized Cacheable get(Object key) {
  // 清理过期对象
  deleteExpiredEntries();
  CacheObject cacheObject = (CacheObject)cachedObjectsHash.get(key);
  if (cacheObject == null) {
   //没找到则未命中次数加一
   cacheMisses++;
   return null;
  }

  //找到则命中次数加一
  cacheHits++;
  //将该缓存对象从lastAccessedList链表中取下并插入到链表头部
  cacheObject.lastAccessedListNode.remove();
  lastAccessedList.addFirst(cacheObject.lastAccessedListNode);
  return cacheObject.object;
 }
}
图一、Jive缓存机制的底层数据结构
 
(二)中间层

中间层
  中间层是联系上层访问接口和低层数据结构的纽带。它的主要功能就是根据ID(对应于数据库中的编号)到缓存中找相应的对象。如果缓存中有该对象就直接得到,没有则去读数据库生成一个新的对象,再把该对象放入缓存中,以便下次访问时能直接得到。图2是相关类的类图。
20068244343.jpg
图二、Jive缓存机制的中间层 

图2中Forum表示论坛,Thread表示论坛贴子的线索,Message表示论坛贴子,它们的关系是:Forum包括数条Thread,Thread包括数条Message。
由图2可见,DbForum类、DbForumThread类和DbForumMessage类的实例对象都包含一个 DbForumFactory类的实例对象factory。DbForum类、DbForumThread类和DbForumMessage类被DbForumFactory生产出来,同时它们也通过DbForumFactory来访问缓存。而在DbForumFactory中则包含一个DatabaseCacheManager类的实例对象cacheManager。它负责管理所有的缓存对象,这些缓存对象就是ForumCache类、ForumThreadCache类和ForumMessageCache类的实例。ForumCache类、 ForumThreadCache类和ForumMessageCache类继承自同一个抽象类DatabaseCache,而在DatabaseCache类中,有一个LongCache型的成员变量cache。这样中间层就和低层的数据结构结合起来了。


  现在以thread线索对象的获得为例,说明中间层是如何运作的。请看代码摘要:

DbForum.java
public class DbForum implements Forum, Cacheable 
{
......
 public ForumThread getThread(long threadID)throws 
   ForumThreadNotFoundException{
  return factory.getThread(threadID, this);
 }
 ......
}

DbForumFactory.java
public class DbForumFactory extends ForumFactory {
    ......
 protected DbForumThread getThread(long threadID, DbForum forum)throws 
     ForumThreadNotFoundException{
  DbForumThread thread = cacheManager.threadCache.get(threadID);
  return thread;
 }
......
}

ForumThreadCache.java
public class ForumThreadCache extends DatabaseCache {
   ......
 public DbForumThread get(long threadID)throws
   ForumThreadNotFoundException{ 
  //缓存中寻找以threadID为编号的DbForumThread对象
  DbForumThread thread = (DbForumThread)cache.get(threadID);
  if (thread == null) {
  //如果在缓存中找不到该对象
   //新建一个以threadID为编号的DbForumThread对象
   thread = new DbForumThread(threadID, factory);
   //将新建对象加入缓存
   cache.add(threadID, thread);
  }
  return thread;
 }
   ......
}

DbForumThread.java
public class DbForumThread implements ForumThread, Cacheable {
   ......
 protected DbForumThread(long id, DbForumFactory factory)throws 
     ForumThreadNotFoundException{
  this.id = id;
  this.factory = factory;
  //读取数据库,其中id对应数据库中的jiveThreadProp表中的threadID字段
  loadFromDb();
  isReadyToSave = true;
 }
   ......
}

从上面的代码可以看到,当调用DbForum类 的getThread(long threadID)方法获得一个编号为threadID的线索对象时,实际上调用的是DbForumFactory类中的getThread(long threadID, DbForum forum)方法,而GetThread方法则是调用ForumThreadCache类的get方法来完成任务的。ForumThreadCache类里get(long threadID)方法则根据threadID到缓存中找相应的线索对象,如果缓存中有该对象就直接得到,没有则新建一个DbForumThread对象,再把该对象放入缓存中。看到这里也许有人会奇怪,好像程序中根本没有连接数据库的语句。我们可以从DbForumThread类的代码中找到答案。原来Jive在新建一个DbForumThread对象时,就已经用loadFromDb()方法把数据读出来了。另一方面,如果在缓存中找到了DbForumThread对象,程序根本就不会新建DbForumThread对象,因而就好象没有数据库的操作,这实际上就是通过缓存机制所要达到的目的。
  Message帖子对象的获得与Thread对象的获得类似,因此就不再重复了。从上面介绍可以看出,只要得到论坛线索的编号threadID,就可以得到对应的线索对象,不管它是从缓存中来,还是从数据库中来。那么threadID是如何从Jsp页面传到中间层的呢?让我们来看上层访问接口的运行机制吧。 

 

(三)上层访问接口

20068242968.jpg

图三、Jive缓存机制的上层访问接口
  先来看forum.jsp和DbForum 类的部分代码:

forum.jsp
<%
// ResultFilter结果过滤类
ResultFilter filter = new ResultFilter();
filter.setStartIndex(start);
filter.setNumResults(range);
//调用Dbforum的threads()方法,获得ForumThreadIterator对象实例
ForumThreadIterator threads = forum.threads(filter);
......
while (threads.hasNext()) {
 //对thead进行遍历
 ForumThread thread = (ForumThread)threads.next();
 //得到thread的ID
 long threadID = thread.getID();
 //得到线索的根帖子rootMessage
 ForumMessage rootMessage = thread.getRootMessage();
 //得到帖子主题和作者等信息
 String subject = rootMessage.getSubject();
 User author = rootMessage.getUser();
 ......
}
%>
DbForum.java
public class DbForum implements Forum, Cacheable {
 ......
 public ForumThreadIterator threads(ResultFilter resultFilter) {
  //生成SQL语句
  String query = getThreadListSQL(resultFilter, false);
  //得到threadID块
  long [] threadBlock = getThreadBlock(query.toString(),
  resultFilter.getStartIndex());
  ......
  //返回ForumThreadBlockIterator对象
  return new ForumThreadBlockIterator(threadBlock, query.toString(),
  startIndex, endIndex, this.id, factory);
 }
 protected long[] getThreadBlock(String query, int startIndex) {
  int blockID = startIndex / THREAD_BLOCK_SIZE;
  int blockStart = blockID * THREAD_BLOCK_SIZE;  
  String key = query + blockID;     
  //根据Key的值到缓存中取得ThreadID的数组
  CacheableLongArray longArray =(CacheableLongArray)threadListCache.get(key);
  //在缓存中则返回
  if (longArray != null) {
   long [] threads = longArray.getLongArray();
   return threads;
  }
  // 否则到数据库中取ThreadID的块,以数组形式返回
  else {
   LongList threadsList = new LongList(THREAD_BLOCK_SIZE);
   Connection con = null;
   Statement stmt = null;
   ...数据库操作 ...
  }
  long [] threads = threadsList.toArray();
  //将 ThreadID的块加入缓存
  threadListCache.add(key, new CacheableLongArray(threads));
  return threads;
 }
......
}

在forum.jsp中有一个ResultFilter类的实例resultFilter。它给出页面显示Thread的起始位置和数量,并作为参数传入forum.threads()中,用于构造相关的SQL语句。当调用forum.threads(filter)时,程序将生成的SQL语句传入到getThreadBlock()方法中得到一个threadID的块,也就是一组threadID。

之所以要读threadID块,是因为显示论坛时并不是显示一条线索就行了,而是一下显示十几条。这样做可以避免反复读数据库,而且threadID不是thread对象,并不占太大空间。
  应该说使用了块以后,减轻了数据库的访问量,因而论坛的效率有了很大的提高。不仅如此,Jive又把块放入了缓存中。在getThreadBlock()方法里,Jive用Cache类的实例对象threadListCache来缓存threadID块,而关键字就是SQL语句加上blockID。也就是说,只要SQL语句和blockID相同,就可以在缓存中取出相同的threadID块。当然,缓存中找不到,还是要到数据库中读出来加入缓存的,这样论坛的效率又得到了进一步的提升。
  ForumThreadBlockIterator类继承自ForumThreadIterator抽象类,而ForumThreadIterator类又实现了Iterator接口,因此得到ForumThreadBlockIterator的实例对象threads后,就可以在用threads.next()方法对它进行编历了。ForumThreadBlockIterator类的功能就是逐个读取ThreadID,然后根据ThreadID返回Thread对象,由此上层访问接口就和中间层衔接起来了。

 

小结:
  Jive的缓存机制值得学习的地方有很多,比如读取线索时不是读一条而是读一个block;显示线索的起始位置和数量用专门的一个类来管理,并且动态生成SQL语句;用一个专门的类来负责管理缓存;把论坛缓存对象的功能抽象出来形成一个缓存的抽象类DatabaseCache,让它去跟低层数据结构联系起来等。这些都体现了面向对象的设计原则,即提高软件的可维护性和可复用性。
  同时,Jive也告诉我们,要想编好程序,只懂条件语句和循环语句可不行,还要必须选择好的数据结构,掌握好的面向对象的设计原则,熟悉设计模式思想方法,这样才能编写出强壮、高效的代码。

转载于:https://www.cnblogs.com/cxccbv/archive/2009/04/11/1433666.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值