Jive
				论坛的一个主要特点就是其性能速度快,因此很多巨大访问量的网站都采用了
				Jive
				论坛。这些都是由于
				Jive
				采取了高速缓存机制。
		
		
				缓存(
				Cache
				)机制是提高系统运行性能必不可少的技术。缓存机制从原理上讲比较简单,就是在原始数据第一次读取后保存在内存中,下次读取时,就直接从内存中读取。原始数据有可能保存在持久化介质或网络上。缓存机制也是代理模式的一种实现。
		
		
				
				
				
				
				
				
				
						4.1  
				
				缓存原理和实现
		
		
				Jive
				的
				Cache
				总体来说实现得不是非常精简和有效。它是针对每个具体数据对象逐个实现缓冲,这种“穷尽”的办法不是实践所推荐的用法。通过使用动态代理模式,可以根据具体方法的不同来实现缓存是值得推荐的做法。
				Jive
				的缓存实现得比较简单,可以用来学习和研究缓存机制。
		
		
				Jive
				中的
				Cache
				实现了缓存机制的大部分行为,它是将对象用惟一的关键字
				Key
				作标识保存在
				HashMap
				或
				Hashtable
				中。当然,必须知道这些对象的大小,这个前提条件的设定可以保证缓存增长时不会超过规定的最大值。
		
		
				如果缓存增长得太大,一些不经常被访问的对象将首先从缓存中删除。如果设置了对象的最大生命周期时间,即使这个对象被反复频繁访问,也将从缓存中删除。这个特性可以适用于一些周期性需要刷新的数据,如来自数据库的数据。
		
		
				在
				Cach
				中除了
				getObject()
				方法的性能依据缓存大小,其他方法的性能都是比较快的。一个
				HashMap
				用来实现快速寻找,两个
				LinkedList
				中一个以一定的访问顺序来保存对象,叫
				accessed LinkedList
				;另外一个以它们加入缓存的顺序保存这些对象,这种保存对象只是保存对象的引用,叫
				 age LinkedList
				。注意,这里的
				LinkedList
				不是
				JDK
				中的
				LinkedList
				,而是
				Jive
				自己定义的
				LinkedList
				。
		
		
				当对象被加入缓存时,首先被
				CacheObject
				封装。封装有以下信息:对象大小(以字节计算),一个指向
				accessed LinkedList
				的引用,一个指向
				age LinkedList
				的引用。
		
		
				当从缓存中获取一个对象如
				ObjectA
				时,首先,
				HashMap
				寻找到指向封装
				ObjectA
				等信息的
				CacheObject
				对象。然后,这个对象将被移动到
				accessed LinkedList
				的前面,还有其他一些动作如缓存清理、删除、过期失效等都是在这个动作中一起触发实现的。
		
		
				public class Cache implements Cacheable {
		
		
				    /**
		
		
				     * 
				因为
				System.currentTimeMillis()
				执行非常耗费性能,因此如果
				get
				操作都执行
		
		
				* 
				这条语句将会形成性能瓶颈,
				通过一个全局时间戳来实现每秒更新
		
		
				* 
				当然,这意味着在缓存过期时间计算上有一到几秒的误差
		
		
				     */
		
		
				    protected static long currentTime = CacheTimer.currentTime;
		
		
				    //CacheObject
				对象
		
		
				    protected HashMap cachedObjectsHash;
		
		
				    //accessed LinkedList 
				最经常访问的排列在最前面
		
		
				    protected LinkedList lastAccessedList;
		
		
				    //
				以缓存加入顺序排列,最后加入排在最前面;越早加入的排在最后面
		
		
				    protected LinkedList ageList;
		
		
				    //
				缓存最大限制
				默认是
				128k 
				可根据内存设定,越大性能越高
		
		
				    protected int maxSize =  128 * 1024;
		
		
				    //
				当前缓存的大小
		
		
				    protected int size = 0;
		
		
				    //
				最大生命周期时间,默认是没有
		
		
				    protected long maxLifetime = -1;
		
		
				    //
				缓存的击中率,用于评测缓存效率
		
		
				    protected long cacheHits, cacheMisses = 0L;
		
		
				 
		
		
				    public Cache() {
		
		
				        // 
				构造
				HashMap. 
				默认
				capacity 
				是
				11
		
		
				        // 
				如果实际大小超过
				11
				,
				HashMap
				将自动扩充,但是每次扩充都
		
		
				// 
				是性能开销,因此期初要设置大一点
		
		
				        cachedObjectsHash = new HashMap(103);
		
		
				        lastAccessedList = new LinkedList();
		
		
				        ageList = new LinkedList();
		
		
				    }
		
		
				    public Cache(int maxSize) {
		
		
				        this();
		
		
				        this.maxSize = maxSize;
		
		
				    }
		
		
				    public Cache(long maxLifetime) {
		
		
				        this();
		
		
				        this.maxLifetime = maxLifetime;
		
		
				    }
		
		
				    public Cache(int maxSize, long maxLifetime) {
		
		
				        this();
		
		
				        this.maxSize = maxSize;
		
		
				        this.maxLifetime = maxLifetime;
		
		
				    }
		
		
				    public int getSize() {        return size;    }
		
		
				    public int getMaxSize() {        return maxSize;    }
		
		
				 
		
		
				    public void setMaxSize(int maxSize) {
		
		
				        this.maxSize = maxSize;
		
		
				        // 
				有可能缓存大小超过最大值,需要激活删除清理动作
		
		
				        cullCache();
		
		
				    }
		
		
				    public synchronized int getNumElements() {
		
		
				        return cachedObjectsHash.size();
		
		
				    }
		
		
				 
		
		
				    /**
		
		
				     * 
				增加一个
				Cacheable
				对象
		
		
				* 
				因为
				HashMap
				不是线程安全的,所以操作方法要使用同步
		
		
				* 
				如果使用
				Hashtable
				就不必同步
		
		
				     */
		
		
				    public synchronized void add(Object key, Cacheable object) {
		
		
				        // 
				删除已经存在的
				key
		
		
				        remove(key);
		
		
				        int objectSize = object.getSize();
		
		
				        // 
				如果被缓存对象的大小超过最大值,就放弃
		
		
				        if (objectSize > maxSize * .90) {            return;        }
		
		
				        size += objectSize;
		
		
				        //
				创建一个
				CacheObject
				对象
		
		
				        CacheObject cacheObject = new CacheObject(object, objectSize);
		
		
				        cachedObjectsHash.put(key, cacheObject);  //
				保存这个
				CacheObject
		
		
				        // 
				加入
				accessed LinkedList
				,
				Jive
				自己的
				LinkedList
				在加入时可以返回值
		
		
				        LinkedListNode lastAccessedNode = lastAccessedList.addFirst(key);
		
		
				        // 
				保存引用
		
		
				        cacheObject.lastAccessedListNode = lastAccessedNode;
		
		
				        // 
				加入到
				age LinkedList
		
		
				        LinkedListNode ageNode = ageList.addFirst(key);
		
		
				        // 
				这里直接调用
				System.currentTimeMillis();
				用法值得讨论
		
		
				        ageNode.timestamp = System.currentTimeMillis();
		
		
				        // 
				保存引用
		
		
				        cacheObject.ageListNode = ageNode;
		
		
				        // 
				做一些清理工作
		
		
				        cullCache();
		
		
				    }
		
		
				    /**
		
		
				     * 
				从缓存中获得一个被缓存的对象,这个方法在下面两种情况返回空
		
		
				     *    <li>
				该对象引用从来没有被加入缓存中
		
		
				     *    <li>
				对象引用因为过期被清除
				</ul>
		
		
				     */
		
		
				    public synchronized Cacheable get(Object key) {
		
		
				        // 
				清除过期缓存
		
		
				        deleteExpiredEntries();
		
		
				        //
				以
				Key
				从缓存中获取一个对象引用
		
		
				        CacheObject cacheObject = (CacheObject)cachedObjectsHash.get(key);
		
		
				        if (cacheObject == null) {
		
		
				            // 
				不存在,增加未命中率
		
		
				            cacheMisses++;
		
		
				            return null;
		
		
				        }
		
		
				        // 
				存在,增加命中率
		
		
				        cacheHits++;
		
		
				        // 
				从
				accessed LinkedList
				中将对象从当前位置删除
		
		
				        // 
				重新插入在第一个
		
		
				        cacheObject.lastAccessedListNode.remove();
		
		
				        lastAccessedList.addFirst(cacheObject.lastAccessedListNode);
		
		
				        return cacheObject.object;
		
		
				    }
		
		
				    …
		
		
				}
		
		
				在
				Cache
				中,关键字
				Key
				是一个对象,为了再次提高性能,可以进一步将
				Key
				确定为一个
				long
				类型的整数。
				
				
		
		
		
				建立
				LongCache
				只是为了提高原来的
				Cache
				性能,本身无多大意义,可以将
				LongCache
				看成与
				Cache
				一样的类。
		
		
				LongCache
				的关键字
				Key
				是
				Forum
				、
				ForumThread
				以及
				 ForumMessage
				等
				long
				类型的
				ID
				,值
				Value
				是
				Forum
				、
				ForumThread
				以及
				ForumMessage
				等的对象。这些基本是通过
				DatabaseCacheManager
				实现完成,在主要类
				DbForumFactory
				的初始化构造时,同时构造了
				DatabaseCacheManager
				的实例
				cacheManager
				。
		
		
				前面过滤器功能分析中,
				Message
				对象获得方法的第一句如下:
		
		
				protected ForumMessage getMessage(long messageID, long threadID, long forumID) throws
		
		
				      ForumMessageNotFoundException {
		
		
				    DbForumMessage message = cacheManager.messageCache.get(messageID);
		
		
				    …
		
		
				}
		
		
				其中,
				cacheManager
				是
				DatabaseCacheManager
				的实例,
				DatabaseCacheManager
				是一个缓存
				Facade
				类。在其中包含了
				5
				种类型的缓存,都是针对
				 Jive
				的
				5
				个主要对象,
				DatabaseCacheManager
				主要代码如下:
		
		
				public class DatabaseCacheManager {
		
		
				…
		
		
				    public UserCache userCache;                          //
				用户资料缓存
		
		
				    public GroupCache groupCache;                       //
				组资料缓存
		
		
				    public ForumCache forumCache;                       //Forum
				论坛缓存
		
		
				    public ForumThreadCache threadCache;                //Thread
				主题缓存
		
		
				    public ForumMessageCache messageCache;          //Message
				缓存
		
		
				    public UserPermissionsCache userPermsCache;     //
				用户权限缓存
		
		
				 
		
		
				    public DatabaseCacheManager(DbForumFactory factory) {
		
		
				        …
		
		
				        forumCache = 
		
		
				            new ForumCache(new LongCache(forumCacheSize, 6*HOUR), factory);
		
		
				        threadCache = 
		
		
				            new ForumThreadCache(
		
		
				                  new LongCache(threadCacheSize, 6*HOUR), factory);
		
		
				        messageCache = new ForumMessageCache(
		
		
				                  new LongCache(messageCacheSize, 6*HOUR), factory);
		
		
				        userCache = new UserCache(
		
		
				                  new LongCache(userCacheSize, 6*HOUR), factory);
		
		
				        groupCache = new GroupCache(
		
		
				                  new LongCache(groupCacheSize, 6*HOUR), factory);
		
		
				        userPermsCache = new UserPermissionsCache(
		
		
				                new UserPermsCache(userPermCacheSize, 24*HOUR), factory
		
		
				        );
		
		
				    }
		
		
				    …
		
		
				}
		
		
				从以上代码看出,
				ForumCache
				等对象生成都是以
				LongCache
				为基础构建的,以
				ForumCache
				为例,代码如下:
		
		
				public class ForumCache extends DatabaseCache {
		
		
				    //
				以
				Cache
				构建
				ID
				缓存
		
		
				    protected Cache forumIDCache = new Cache(128*1024, 6*JiveGlobals.HOUR);
		
		
				    //
				以
				LongCache
				构建整个对象缓存
		
		
				    public ForumCache(LongCache cache, DbForumFactory forumFactory) {
		
		
				        super(cache, forumFactory);
		
		
				    }
		
		
				 
		
		
				    public DbForum get(long forumID) throws ForumNotFoundException {
		
		
				        …
		
		
				        DbForum forum = (DbForum)cache.get(forumID);
		
		
				        if (forum == null) {    //
				如果缓存没有从数据库中获取
		
		
				            forum = new DbForum(forumID, factory);
		
		
				            cache.add(forumID, forum); 
		
		
				        }
		
		
				        return forum;
		
		
				    }
		
		
				 
		
		
				public Forum get(String name) throws ForumNotFoundException {
		
		
				         //
				以
				name
				为
				key
				,从
				forumIDCache
				中获取
				ID
		
		
				 CacheableLong forumIDLong = (CacheableLong)forumIDCache.get(name);
		
		
				        if (forumIDLong == null) { //
				如果缓存没有
				从数据库获得
		
		
				            long forumID = factory.getForumID(name);
		
		
				            forumIDLong = new CacheableLong(forumID); //
				生成一个缓存对象
		
		
				            forumIDCache.add(name, forumIDLong);
		
		
				        }
		
		
				        return get(forumIDLong.getLong());
		
		
				    }
		
		
				    …
		
		
				}
		
		
				由此可以看到,
				LongCache
				封装了
				Cache
				的核心功能,而
				ForumCache
				等类则是在
				LongCache
				核心外又包装了与应用系统相关的操作,这有点类似装饰(
				Decorator
				)模式。
		
		
				从中也可以看到
				Cache
				和
				LongCache
				两种缓存的用法。
		
		
				使用
				Cache
				时的关键字
				Key
				是任何字段。如上面代码中的
				String name
				,如果用户大量帖子主题查询中,
				Key
				是
				query + blockID
				,见
				DbForum
				中的
				getThreadBlock
				方法;而值
				Value
				则是
				Long
				类型的
				ID
				,如
				ForumID
				或
				ThreadID
				等。
		
		
				LongCache
				的关键字
				Key
				是
				Long
				类型的
				ID
				,如
				ForumID
				或
				ThreadID
				等;而值
				Value
				则是
				Forum
				、
				ForumThread
				或
				ForumMessage
				等主要具体对象。
		
		
				在实际使用中,大多数是根据
				ID
				获得对象。但有时并不是这样,因此根据应用区分了两种
				Cache
				,这其实类似数据库的数据表,除了主关键字外还有其他关键字。
		
		
		
				缓存中对象是原对象的映射,如何确保缓存中对象和原对象的一致性?即当原对象发生变化时,缓存中的对象也必须立即更新。这是缓存机制需要解决的另外一个基本技术问题。
		
		
				Jive
				中是在原对象发生变化时,立即进行清除缓存中对象,如
				ForumMessage
				对象的创建。在
				DbForumThread
				的
				AddMessage
				方法中有下列语句:
		
		
				factory.cacheManager.threadCache.remove(this.id);
		
		
				factory.cacheManager.forumCache.remove(this.forumID);
		
		
				即当有新的帖子加入时,将
				ForumThreadCache
				和
				ForumCache
				相关缓冲全部清除。这样,当有相关对象读取时,将直接从数据库中读取,这是一种非常简单的缓存更新方式。
		
		
				在复杂的系统,例如有一台以上的服务器运行着
				Jive
				系统。如果一个用户登陆一台服务器后,通过这台服务器增加新帖。那么按照上述原理,只能更新本服务器
				JVM
				中的缓存数据,而其他服务器则无从得知这种改变,这就需要一种分布式的缓存机制。
		
		
				
						
								
										| 
														
																  
														图
														3-7  Jive
														主要对象的访问
												 | 
						
				
		 
		
				到目前可以发现
				,
				整个
				Jive
				系统其实是围绕
				Forum
				、
				ForumThread
				和
				ForumMessage
				等这些主要对象展开的读取、修改或创建等操作。由于这些对象原先持久化保存在数据库中,为了提高性能和加强安全性,
				Jive
				在这些对象外面分别实现两层包装,如图
				3-7
				所示。
		
		
				客户端如果需要访问这些对象,首先要经过它们的代理对象。进行访问权限的检查,然后再从缓存中获取该对象。只有缓存不存在时,才会从数据库中获取。
		
		
				这套机制是大多数应用系统都面临的必须解决的基本功能,因此完全可以做成一个通用的可重复使用的框架。这样在具体应用时,不必每个应用系统都架设开发这样的机制。其实
				EJB
				就是这样一套框架,实体
				Bean
				都由缓存机制支持,而通过设定
				ejb-jar.xml
				可以实现访问权限控制,这些工作都直接由
				EJB
				容器实现了,不必在代码中自己来实现。剩余的工作是调整
				EJB
				容器的参数,使之适合应用系统的具体要求,这些将在以后章节中讨论。
		
		
				在
				Jive
				中,图
				3-7
				的机制是通过不同方式实现的。基本上是一配二模式:一个对象有一个缓冲对象和一个代理对象,这样做的一个缺点是导致对象太多,系统变得复杂。这点在阅读
				Jive
				源码时可能已经发现。
		
		
				如果建立一个对象工厂,工厂内部封装了图
				3-7
				机制实现过程,客户端可以根据不同的工厂输入参数获得具体不同的对象。这样也许代码结构要更加抽象和紧凑,
				Java
				的动态代理
				API
				也许是实现这个工厂的主要技术基础。有兴趣者可以进一步研究提炼。