Java开发网 Java开发网
注册 | 登录 | 帮助 | 搜索 | 排行榜 | 发帖统计  

您没有登录

» Java开发网 » Java SE 综合讨论区  

按打印兼容模式打印这个话题 打印话题    把这个话题寄给朋友 寄给朋友    该主题的所有更新都将Email到你的邮箱 订阅主题
flat modethreaded modego to previous topicgo to next topicgo to back
作者 剖析Jive的缓存机制(ZT)
nerd





发贴: 42
积分: 0
于 2003-05-17 23:47 user profilesend a private message to usersearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
发信人: bitilittlebe (浪漫苦瓜), 信区: Java
标 题: 剖析Jive的缓存机制(ZT)
发信站: BBS 水木清华站 (Sat May 17 15:03:51 2003), 转信

剖析Jive的缓存机制
前言
Jive是一个广受欢迎的开放的源码的论坛项目,它有很多值得我们学习的地方。这篇文章
谈的就是Jive缓存机制的实现,希望对大家有所帮助。
简介
我们知道,在两个存取速度差距很大的对象(比如数据库和内存)之间,通常要加一个缓
存,来匹配二者的速度。因此,缓存机制在我们实际项目中还是经常遇到的。同样Jive也
使用缓存来加快贴子的显示。如果你正试图编写一个类似的东西,不妨研究一下Jive源码
,可能对你大有帮助。
在Jive2.1.2中,涉及jive的缓存机制的java类大致可以分为以下四个部分(为了简化起

,本文只讨论帖子的缓存机制的实现,用户名和权限的存取虽然也用到了的缓存,但其实
现机制与前者类似,因此不再赘述):
第一部分:提供HashMap,LinkedListedlist等数据结构以便实现缓存机制,其中HashMap

JDK提供的,其Key类型为Object。你可以在com.jivesoftware.util包中找到他们,包括

类有Cache类, LinkedList类,LinkedListNode类和Casheable接口,CacheObject类,Cac
heableBoolean类,CacheableInt类,CacheableLong类,CacheableLongArray类,Cachea
bleString类,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类, F
orumThreadCache类,ForumMessageCache类;
第四部分:向Jsp页面提供访问接口,同样在com.jivesoftware.forum.database包中,包
括类有ForumThreadBlockIterator和ForumMessageBlockIterator类,第三部分的DbForum
类,DbForumThread类和DbForumMessage 类也可以包括进来,实际上,这三个类是第三和
第四部分联系的纽带,在com.jivesoftware.util包中还有一个LongList类,它用来将For
umThreadBlockIterator和ForumMessageBlockIterator转化成Long型数组,因此也应算在
这部分;
因此缓存机制也可以划分为三层,即第一、二部分的底层数据结构、第三部分的中间层和
第四部分的上层访问接口,下面分别讨论:
底层数据结构
jive缓存机制的原理其实很简单,就是把所要缓存的对象加到HashMap哈希映射表中,用

个LinkedListedlist双向链表分别维持着缓存对象和每个缓存对象的生命周期,如果一个
缓存对象被访问到,那么就把它放到链表的最前面,然后不定时的把要缓存对象的对象加
入链表中,把过期对象删除,如此反复。我们比较一下第一和第二部分就可以发现,他们
的代码几乎完全相同。差别就在第二部分的哈希映射表没有采用JDK提供的类,而是采用

作者自己编写的一个类,他将原来哈希映射表的Key的类型由Object改为Long,这样做虽

在一定程度上加快了缓存的速度并减小了缓存的大小,但无形之中也减低了程序的稳定性
和可读性,因此不推荐大家仿效。值得一提的是,在Jive1.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构成了Link
edList链表,而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(),它要求继承类实现该方法

报占用缓存的大小,以便实施管理。Integer,Boolean,Long,LongArray,String类型都
有其对应的类,CacheSizes类则把各种类型的长度封装起来,便于修改和移植。那么为什
么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;
//缓存元素的最大尺寸128kbit,可修改
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;
}
到这里第一部分的Java类实现就说完了,正如上文提到的那样,第二部分的Java类实现与
第一部分基本上没有什么差别,因此就不再赘述。下面给出第二部分的类图,以供读者参
考。

中间层
中间层是联系上层访问接口和低层数据结构的纽带。它的主要功能就是根据ID(对应
于数据库中的编号)到缓存中去找相应的对象,如果缓存中有该对象就直接得到,没有则

读数据库生成一个新的对象,再把该对象放入缓存中,以便下次访问时能直接得到。下面
是相关类的类图:.
(注:Forum表示论坛,Thread表示论坛贴子的线索,Message表示论坛贴子,它们的关系
是这样的 :Forum包括数条Thread,Thread包括数条Message。)
由上图可见,DbForum类,DbForumThread类和DbForumMessage 类的实例对象都包含一个
DbForumFactory类的实例对象factory。DbForum,DbForumThread和DbForumMessage被DbF
orumFactory生产出来,同时他们也通过DbForumFactory来访问缓存。而在DbForumFactor
y中则包含一个DatabaseCacheManager类的实例对象cacheManager,它负责管理所有的缓

对象,如图,这些缓存对象就是ForumCache类, ForumThreadCache类和ForumMessageCach
e类的实例。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类中的ge
tThread(long threadID, DbForum forum)方法,而它则是调用ForumThreadCache类的方

来完成任务的。那么ForumThreadCache类里get(long threadID)方法有做了什么呢?代码
已经很清楚了,就是根据threadID到缓存中去找相应的线索对象,如果缓存中有该对象就
直接得到,没有则新建一个DbForumThread对象,再把该对象放入缓存中。看到这里,有

让人奇怪,好象程序中根本没有连接数据库的语句。再看DbForumThread类的代码,终于

然大悟,原来Jive在新建一个DbForumThread对象就已经用loadFromDb()方法把数据读出

了;另一方面,如果在缓存中找了DbForumThread对象,程序根本就不会新建DbForumThre
ad对象,因而就好象没有数据库的操作,这实际上就是我们通过缓存机制所要达到的目的

Message帖子对象的获得与Thread对象的获得类似,因此就不再重复了。从上面我们

以看到,只要我们得到了论坛线索的编号threadID,我们就可以得到对应的线索对象,不
管它是从缓存中来,还是从数据库中来。那么threadID是如何从Jsp页面传到中间层的呢

让我们来看上层访问接口的运行机制吧。
上层访问接口
上层访问接口的主要功能连接Jsp页面和中间层,换句话说,就是把Jsp页面中要调用
的Thread、Message对象的ID传递到中间层。下面给出访问Thread的相关类的类图(访问M
essage机制类似,故省略)。其中的forum.jsp是显示论坛内容的页面,在这里,我们把f
orum.jsp看成是一个特殊的类,它里面有一个ForumThreadIterator类的实例变量threads
和DbForum类的实例变量forum,故它和ForumThreadIterator类及DbForum类的关系应是关
联关系。

先来看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语句。当调用foru
m.threads(filter)时,程序将生成的SQL语句传入到getThreadBlock()方法中去得到一个
threadID的块,也就是一组threadID。之所以要读threadID块,是因为显示论坛时并不是
显示一条线索就行了,而是一下显示十几条,这样做可以避免反复读数据库,再说thread
ID不是thread对象,并不怎么占空间。
应该说,使用了块以后,减轻了数据库的访问量,因而论坛的效率有了很大的提高。然而
好戏还在后头,Jive又把块放入了缓存中。在getThreadBlock()方法里,Jive用Cache类

实例对象threadListCache来缓存threadID块,而关键字就是SQL语句加上blockID,也就

说,主要SQL语句和blockID相同,就可以在缓存中取出相同的threadID块。当然,缓存中
找不到,还是要到数据库中读出来加入缓存的。这样论坛的效率又得到了进一步的提升。

ForumThreadBlockIterator类继承自ForumThreadIterator抽象类,而ForumThreadIterat
or类又实现了Iterator接口,因此得到ForumThreadBlockIterator的实例对象threads后

就可以在用threads.next()方法对它进行编历了。ForumThreadBlockIterator类的代码想
都可以想得到,就是逐个读取ThreadID,然后根据ThreadID返回Thread对象,由此上层访
问接口就和中间层衔接起来了。
小结
Jive的缓存机制值得我们学习的地方有很多,比如读取线索时不是读一条而是读一个bloc
k;显示线索的起始位置和数量用专门的一个类来管理,动态生成SQL语句;用一个专门的
类来负责管理缓存;把论坛缓存对象的功能抽象出来形成一个缓存的抽象类DatabaseCach
e,让它去跟低层数据结构联系起来。这些都体现了面向对象的设计原则即提高软件的可

护性和可复用性。
同时,Jive也告诉我们,要想编好程序,只懂条件语句和循环语句可不行;必须选择好数
据结构,掌握好面向对象的设计原则,熟悉设计模式思想方法,这样才能编写出强壮高效
的代码。
作者TomJava Email: tomjava@sohu.com
2003年3月2日




话题树型展开
人气 标题 作者 字数 发贴时间
4045 剖析Jive的缓存机制(ZT) nerd 13426 2003-05-17 23:47
2825 Re:剖析Jive的缓存机制(ZT) robornet 9 2003-05-18 15:10
2905 Re:剖析Jive的缓存机制(ZT) feynman 6 2003-05-19 10:50

flat modethreaded modego to previous topicgo to next topicgo to back
  已读帖子
  新的帖子
  被删除的帖子
Jump to the top of page

   Powered by Jute Powerful Forum® Version Jute 1.5.6 Ent
Copyright © 2002-2021 Cjsdn Team. All Righits Reserved. 闽ICP备05005120号-1
客服电话 18559299278    客服信箱 714923@qq.com    客服QQ 714923