前面的文章已经谈了roller是如何生成页面的,其实页面就是一些HTML代码。在roller中,使用一个类CachedContent对象来包装这些HTML代码,然后才把CachedContent对象放到缓存中以便以后使用。
谈到缓存,roller的开发者们绝不会放过任何一个提高性能的机会。首先利用浏览器的缓存功能,见如下代码:
if (ModDateHeaderUtil.respondIfNotModified(request, response, lastModified)) { return; } else { // set last-modified date ModDateHeaderUtil.setLastModifiedHeader(response, lastModified); }
接下来谈谈roller如何为了提高性能如何利用内存来实现缓存的。这里说的主要针对博客页面的缓存处理。为了叙述方便,下面的是笔者根据源代码整理的类图:
在图中,有两个单例类,分别是CacheManager和WeblogPageCache。其中WeblogPageCache有一个Cache属性,在实例化时通过CacheManager实例获取一个Cache对象。
而CacheManager实例化时要通过读取属性文件来初始化cacheFactory属性,默认情况下cacheFactory是一个ExpiringLRUCacheFactoryImpl对象。这里应用了工厂模式。
通过图中我们可以知道WeblogPageCache的contentCache属性实际是一个ExpiringLRUCacheImpl对象。
我们知道缓存很好用,put进去get出来。看起来简单,但真正的缓存系统要做很多工作,比如是否过期了,如何清理缓存等等。
Roller中定义了一个Cache接口,代表一个缓存。有两个实现类,分别是LRUCacheImpl和ExpiringLRUCacheImpl,他们是父子关系。实现了LRU算法的缓存策略。作为缓存接口自然少不了这几个重要的方法:put、get、remove、clear。
WeblogPageCache类为我们屏蔽了很多实现细节,如果是作为缓存的使用者,只要了解WeblogPageCache的API就可以了。WeblogPageCache同样也有和Cache类似的方法,这些对应的方法其实是把请求委派给Cache来处理。WeblogPageCache还有一个重要的方法generateKey,生产key很重要,但笔者不打算在这里讲。
通过类图和前面的文字介绍,我们可以知道系统是如何准备好缓存等待我们使用的了。
前面提到html被包装到CachedContent对象中,当页面需要缓存时,WeblogPageCache的put方法被调用,传入一个key和CachedContent对象。同时new一个LazyExpiringCacheEntry对象来包装CachedContent。为什么这样做呢?目的是为了在get时判断数据是否有效。
先说说本人对缓存的两个概念(有效和过期)的理解。有效是指被缓存的数据已经被修改过了;过期是指当被缓存的数据超过了缓存系统设定的时间长度。
WeblogPageCache在put时,其实是把请求委派给Cache对象的,而这个Cache就是一个ExpiringLRUCacheImpl对象。ExpiringLRUCacheImpl的put方法被调用时,又new了一个ExpiringCacheEntry对象来包装LazyExpiringCacheEntry对象。这样做的目的是为了在get时判断被缓存数据是否过期。
缓存的put过程就是这样的,下面说说如何从缓存中取出数据,取出数据是通过调用get方法。
当想获取一个缓存数据时需要调用WeblogPageCache的get方法,此方法需要传入一个key和一个博客最近修改时间。Get的过程不是很好描述。WeblogPageCache把请求传给Cache对象上的get方法。在Cache的get方法中通过key从Map中取出value,这个value先是一个ExpiringCacheEntry对象,因为此对象被new时初始化了超时时间和缓存时间。所以通过
public boolean hasExpired() { long now = System.currentTimeMillis(); return ((this.timeCached + this.timeout) < now); }
可以判断一个数据是否过期。如果过期就被从缓存中删除了,否则就从ExpiringCacheEntry中取出value(也就是LazyExpiringCacheEntry)返回。
一个数据不过期并不代表能使用,还要判断他是否有效。程序控制权回到WeblogPageCache的get方法。如果Cache的get方法不是null,就要判断是否有效。LazyExpiringCacheEntry对象被new出来时,它的被缓存时间也同时被初始化。由于它是包装CachedContent对象的,因此它也有个get方法,参数是最近修改时间。在此方法中,通过判断最近时间是否大于缓存时间来确定数据是否有效。
如果数据是有效的就把CachedContent返回,否则就返回null。整个缓存的get过程就这样结束了。
体会:roller的缓存用了工厂模式,这样的好处是:当我们的站点规模变大时,roller缓存可能满足不了要求,这时如果使用优秀的缓存系统(比如memcached)是比较容易集成到roller中的。
欢迎大家一起讨论roller。。。。。。