Cache是一种用于提高系统响应速度、改善系统运行性能的技术。尤其是在Web应用中,通过缓存页面的输出结果,可以很显著的改善系统运行性能。本文中作者给大家介绍一个实现J2EE框架中Web应用层缓存功能的开放源代码项目----OSCache。通过应用OSCache,我们不但可以实现通常的Cache功能,还能够改善系统的稳定性。
1 面临的问题
1.1 需要处理的特殊动态内容
在信息系统建设过程中我们通常会遇到这样的问题:
1. 基础数据的变更问题
信息系统中需要处理的基础数据的内容短时间内是不会发生变化的,但是在一个相对长一些的时间里,它却可能是动态增加或者减少的。
举个例子:电子商务中关于送货区域的定义,可能短时间内不会发生变化,但是随着电子商务企业业务的扩大,系统中需要处理的送货区域就可能增加。所以我们的系统中不得不在每次向客户展示送货区域信息的时候都和数据库(假设送货区域信息保存在数据库中,这也是通常采用的处理方法)进行交互。
2. 统计报表(不仅限于统计报表)的问题
一般来说,统计报表是一个周期性的工作,可能是半个月、一个月或者更长的时间才会需要更新一次,然而统计报表通常是图形显示或者是生成pdf、word、excel等格式的文件,这些图形内容、文件的生成通常需要消耗很多的系统资源,给系统运行造成很大的负担。
1.2 问题的共同点
通过比较分析,不难发现这两类问题有一些共同点:
1、被处理的内容短时间不变,所以短时间内可以作为静态内容进行处理
2、在一个不太长的时间内,被处理的内容可能或者必定产生变化,所以必须将他们作为动态内容进行处理
3、在合理的时间区段内可以忽略被处理内容变化后带来的影响
4、对这些内容的处理动作比较消耗系统性能,影响系统响应时间
1.3 解决方法
缓存技术可以帮助我们很好的解决这个问题:
1、缓存信息
当上述的基础数据或者统计报表第一次被访问时,被处理的内容被当作动态信息,基础数库从数据库中获得,统计报表也会被生成符合要求的图形、文件,然后这些信息都会被放入缓存信息中。
2、响应信息由缓存提供
当上述的基础数据或者统计报表继续被访问时,系统将会首先检查缓存信息中是否有对应的内容和我们设定的缓存规则,如果符合缓存信息存在而且符合缓存规则,给出的响应将来自于缓存信息,如果没有或者缓存信息已经不符合设定的要求,系统将重复上一步的动作。
很显然,上面的步骤2中,多数情况下,当用户请求到达时,被处理的内容将来自于缓存,所以大大的减少了与数据库的交互,或者不再需要为每个请求都生成一次报表图形或者文件,这部分工作的减少对于降低系统性能消耗、提高系统稳定性和并发处理能力是非常有益的。
2 OSCache简介
OSCache是OpenSymphony组织提供的一个J2EE架构中Web应用层的缓存技术实现组件,它的出现解决了我们面临的问题。 OSCache目前最新的稳定版本是2.3.2,本文中的例子都是基于这个版本的,如果大家运行例子的过程中发生问题,请首先确认是否采用了正确的软件版本。
2.1 主要特征
1. 兼容多种支持JSP的web服务器
已经通过兼容测试的web服务器包括OrionServer (1.4.0或者以上版本) 、Macromedia JRun (3.0或者以上版本) 、BEA Weblogic (7.x或者以上版本) 、IBM Websphere (5.0版本)、Silverstream (3.7.4版本)、Caucho Resin (1.2.3或者以上版本)、Tomcat (4.0或者以上版本) ,其他支持servlet2.3、jsp1.2的web服务器应该都是完全兼容OSCache的。
2. 可选的缓存区
你可以使用内存、硬盘空间、同时使用内存和硬盘或者提供自己的其他资源(需要自己提供适配器)作为缓存区。
- 使用内存作为缓存区将可以提供更好的性能
- 使用硬盘作为缓存区可以在服务器重起后迅速恢复缓存内容
- 同时使用内存和硬盘作为缓存区则可以减少对内存的占用
3. 灵活的缓存系统
OSCache支持对部分页面内容或者对页面级的响应内容进行缓存,编程者可以根据不同的需求、不同的环境选择不同的缓存级别。
4. 容错
在一般的web应用中,如果某个页面需要和数据库打交道,而当客户请求到达时,web应用和数据库之间无法进行交互,那么将返回给用户"系统出错"或者类似的提示信息,如果使用了OSCache的话,你可以使用缓存提供给用户,给自己赢得维护系统或者采取其他补救的时间。
其它特性还包括对集群的支持、缓存主动刷新等特性,大家可以参考OpenSymphony网站上的其他资源获取更多的信息。
3 OSCache组件的安装
OSCache是一个基于web应用的组件,他的安装工作主要是对web应用进行配置,大概的步骤如下:
1. 下载、解压缩OSCache
请到OSCache的主页http://www.opensymphony.com/oscache/download.html下载Oscache的最新版本,作者下载的是OSCache的最新稳定版本2.0。
将下载后的。Zip文件解压缩到c:oscache(后面的章节中将使用%OSCache_Home%来表示这个目录)目录下
2. 新建立一个web应用
3. 将主要组件%OSCache_Home%oscache.jar放入WEB-INFlib目录
4. commons-logging.jar、commons-collections.jar的处理
- OSCache组件用Jakarta Commons Logging来处理日志信息,所以需要commons-logging.jar的支持,请将%OSCache_Home%libcorecommons-logging.jar放入classpath(通常意味着将这个文件放入WEB-INFlib目录)
- 如果使用JDK1.3,请将%OSCache_Home%libcorecommons-collections.jar放入classpath,如果使用JDK1.4或者以上版本,则不需要了
5. 将oscache.properties、oscache.tld放入WEB-INFclass目录
- %OSCache_Home%oscache.properties包含了对OSCache运行特征值的设置信息
- %OSCache_Home%oscache.tld包含了OSCache提供的标签库的定义内容
6. 修改web.xml文件
在web.xml文件中增加下面的内容,增加对OSCache提供的taglib的支持:
<taglib>
<taglib-uri>oscache</taglib-uri>
<taglib-location>/WEB-INF/classes/ oscache.tld</taglib-location>
</taglib>
|
4 开始使用OSCache中的缓存组件
OSCache中按照缓存范围的不同分为两种不同的方式:一种是缓存JSP页面中部分或者全部内容,一种是基于整个页面文件的缓存。
4.1 JSP部分内容缓存
4.1.1 Cache-OSCache提供的缓存标签
这是OSCache提供的标签库中最重要的一个标签,包括在标签中的内容将应用缓存机制进行处理,处理的方式将取决于编程者对cache标签属性的设置。
第一次请求到达时,标签中的内容被处理并且缓存起来,当下一个请求到达时,缓存系统会检查这部分内容的缓存是否已经失效,主要是以下几项:
- 1. 缓存时间超过了cache标签设置的time或者duration属性规定的超时时间
- 2. cron属性规定的时间比缓存信息的开始时间更晚
- 3. 标签中缓存的内容在缓存后又被重新刷新过
- 4. 其他缓存超期设定
如果符合上面四项中的任何一项,被缓存的内容视为已经失效,这时被缓存的内容将被重新处理并且返回处理过后的信息,如果被缓存的内容没有失效,那么返回给用户的将是缓存中的信息。
cache标签的属性说明:
key - 标识缓存内容的关键词。在指定的作用范围内必须是唯一的。默认的key是被访问页面的URI和后面的请求字符串。
你可以在同一个页面中使用很多cache标签而不指定他的key属性,这种情况下系统使用该页面的URI和后面的请求字符串,另外再自动给这些key增加一个索引值来区分这些缓存内容。但是不推荐采用这样的方式。
scope - 缓存发生作用的范围,可以是application或者session
time - 缓存内容的时间段,单位是秒,默认是3600秒,也就是一个小时,如果设定一个负值,那么这部分被缓存的内容将永远不过期。
duration - 指定缓存内容失效的时间,是相对time的另一个选择,可以使用简单日期格式或者符合USO-8601的日期格式。如:duration='PT5M' duration='5s'等
refresh - false 或者true。
如果refresh属性设置为true,不管其他的属性是否符合条件,这部分被缓存的内容都将被更新,这给编程者一种选择,决定什么时候必须刷新。
mode - 如果编程者不希望被缓存的内容增加到给用户的响应中,可以设置mode属性为"silent"
其它可用的属性还包括:cron 、groups、language、refreshpolicyclass、refreshpolicyparam。
上面的这些属性可以单独使用,也可以根据需要组合使用,下面的例子将讲解这些常用属性的使用方式。
4.1.2 Cache标签实例分析:
1. 最简单的cache标签用法
使用默认的关键字来标识cache内容,超时时间是默认的3600秒
<cache:cache>
<%
//自己的JSP代码内容
%>
</cache:cache>
|
2. 用自己指定的字符串标识缓存内容,并且设定作用范围为session。
<cache:cache key="foobar" scope="session">
<%
//自己的JSP代码内容
%>
</cache:cache>
|
3.动态设定key值,使用自己指定的time属性设定缓存内容的超时时间,使用动态refresh值决定是否强制内容刷新。
因为OSCache使用key值来标识缓存内容,使用相同的key值将会被认为使用相同的的缓存内容,所以使用动态的key值可以自由的根据不同的角色、不同的要求决定使用不同的缓存内容。
<cache:cache key="<%= product.getId() %>" time="1800" refresh="<%= needRefresh %>">
<%
//自己的JSP代码内容
%>
</cache:cache>
|
4. 设置time属性为负数使缓存内容永不过期
<cache:cache time="-1">
<%
//自己的JSP代码内容
%>
|
5. 使用duration属性设置超期时间
<cache:cache duration='PT5M'>
<%
//自己的JSP代码内容
%>
|
6. 使用mode属性使被缓存的内容不加入给客户的响应中
<cache:cache mode='silent'>
<%
//自己的JSP代码内容
%>
|
4.2 用CashFilter实现页面级缓存
在OSCache组件中提供了一个CacheFilter用于实现页面级的缓存,主要用于对web应用中的某些动态页面进行缓存,尤其是那些需要生成pdf格式文件/报表、图片文件等的页面,不仅减少了数据库的交互、减少数据库服务器的压力,而且对于减少web服务器的性能消耗有很显著的效果。
这种功能的实现是通过在web.xml中进行配置来决定缓存哪一个或者一组页面,而且还可以设置缓存的相关属性,这种基于配置文件的实现方式对于J2EE来说应该是一种标准的实现方式了。
[注] 只有客户访问时返回http头信息中代码为200(也就是访问已经成功)的页面信息才能够被缓存
1. 缓存单个文件
修改web.xml,增加如下内容,确定对/testContent.jsp页面进行缓存。
<filter>
<filter-name>CacheFilter</filter-name>
<filter-class>com.opensymphony.oscache.web.filter.CacheFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CacheFilter</filter-name>
<!-对/testContent.jsp页面内容进行缓存-->
<url-pattern>/testContent.jsp</url-pattern>
</filter-mapping>
|
2. 缓存URL pattern
修改web.xml,增加如下内容,确定对*.jsp页面进行缓存。
<filter>
<filter-name>CacheFilter</filter-name>
<filter-class>com.opensymphony.oscache.web.filter.CacheFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CacheFilter</filter-name>
<!-对所有jsp页面内容进行缓存-->
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
|
3. 自己设定缓存属性
在页面级缓存的情况下,可以通过设置CacheFilter的初始属性来决定缓存的一些特性:time属性设置缓存的时间段,默认为3600秒,可以根据自己的需要只有的设置,而scope属性设置,默认为application,可选项包括application、session
<filter>
<filter-name>CacheFilter</filter-name>
<filter-class>com.opensymphony.oscache.web.filter.CacheFilter</filter-class>
<init-param>
<param-name>time</param-name>
<param-value>600</param-value>
</init-param>
<init-param>
<param-name>scope</param-name>
<param-value>session</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CacheFilter</filter-name>
<!-对所有jsp页面内容进行缓存-->
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
|
5 配置
Configuration:配置oscache.properties
1、cache.memory: true 或者 false。默认为true 不使用内存缓存而使用硬盘缓存是很愚蠢的事情。
2、cache.capacity 缓存object的最大数量值。默认是不限制,cache不会移走任何缓存内容。负数被当作不限制。
3、cache.algorithm 运算规则。为了使用规则,cache的size必须是指定的。 如果cache的size不指定的话,法则将不会限制缓存对象的大小。 如果你指定了cache的size,但不指定algorithm,那它会默认使用:com.opensymphony.oscache.base.algorithm.LRUCache 有下面三种规则: com.opensymphony.oscache.base.algorithm.LRUCache-last in first out,最迟插入的最先调用。默认值。
com.opensymphony.oscache.base.algorithm.FIFOCache -first int first out。 com.opensymphony.oscache.base.algorithm.UnlimitedCache -cache中的内容将永远不会被丢弃。 如果cache.capacity不指定值的话,它将被设为默认。
4、cache.blocking 是否同步化。true 或者 false。一般设为true,避免读取脏数据。
5。cache.unlimited.disk 指定硬盘缓存是否要作限制。默认值为false。false的状况下,disk cache capacity 将和cache.capacity的值相同。
6、cache.persistence.class 指定类是被持久化的类。class必须实现PersistenceListener接口。 作为硬盘持久,可以实现com.opensymphony.oscache.plugins.diskpersistence.HashDiskPersistenceListener接口。 它把class的toString()输出的hash值作为文件的名称。如果你要把文件名易读(自己设定),DiskPersistenceListener 的父类也 能使用,但其可能有非法字符或者过长的名字。
注意:HashDiskPersistenceListener 和 DiskPersistenceListener 需要设定硬盘路径:cache.path
7、cache.path 指定硬盘缓存的路径。目录如果不存在将被建立。同时注意oscache应该要有权限写文件系统。 cache.path=c:\myapp\cache or *ix: cache.path=/opt/myapp/cache
8、cache.persistence.overflow.only (NEW! Since 2.1) 指定是否只有在内存不足的情况下才使用硬盘缓存。 默认值false。但推荐是true如果内存cache被允许的话。这个属性彻底的改变了cache的行为,使得persisted cache 和memory完全不同。
9、cache.event.listeners 用逗号分离的class名列表。每个class必须实现以下接口之一,或者几个 CacheEntryEventListener:接收cache add/update/flush and remove事件 CacheMapAccessEventListener :接收cache访问事件。这个可以让你跟踪cache怎么工作。 默认是不配置任何class的。当然你可以使用一下的class: com.opensymphony.oscache.plugins.clustersupport.BroadcastingCacheEventListener
-分布式的监听器。可以广播到局域网内的其他cache实例。 com.opensymphony.oscache.extra.CacheEntryEventListenerImpl -一个简单的监听器。在cache的生命周期中记录count of 所有entry的事件。 com.opensymphony.oscache.extra.CacheMapAccessEventListenerImpl -记录count of cache map events(cache hits,misses and state hits).
10、cache.key This is the key that will be used by the ServletCacheAdministrator (and hence the custom tags) to store the cache object in the application and session scope. The default value when this property is not specified is "__oscache_cache". If you want
to access this default value in your code, it is available as com.opensymphony.oscache.base.Const.DEFAULT_CACHE_KEY.
11、cache.use.host.domain.in.key If your server is configured with multiple hosts, you may wish to add host name information to automatically generated cache keys. If so, set this property to true. The default value is false.
12、Additional Properties In additon to the above basic options, any other properties that are specified in this file will still be loaded and can be made available to your event handlers. For example, the JavaGroupsBroadcastingListener supports the following
additional properties:
13、cache.cluster.multicast.ip The multicast IP to use for this cache cluster. Defaults to 231.12.21.132.
14、cache.cluster.properties Specifies additional configuration options for the clustering. The default setting is UDP(mcast_addr=231.12.21.132;mcast_port=45566;ip_ttl=32; mcast_send_buf_size=150000;mcast_recv_buf_size=80000): PING(timeout=2000;num_initial_members=3):
MERGE2(min_interval=5000;max_interval=10000): FD_SOCK:VERIFY_SUSPECT(timeout=1500): pbcast.NAKACK(gc_lag=50;retransmit_timeout=300,600,1200,2400,4800;max_xmit_size=8192): UNICAST(timeout=300,600,1200,2400): pbcast.STABLE(desired_avg_gossip=20000): FRAG(frag_size=8096;down_thread=false;up_thread=false):
pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;shun=false;print_local_addr=true)
6 性能测试结果
6.1 测试环境
系统平台:windows 2000 高级服务器/ P3 800 /512M内存
web服务器:websphere 5.0
数据库服务器:mysql 4.0.18-nt
性能测试用工具:apache Jmeter
6.2 测试计划
这次性能测试对比方为使用缓存和不使用缓存两种,他们的访问代码都是一样的:通过数据源从本地mysql数据库中获取person表的所有记录,然后显示在页面上。
测试中将模仿10个用户,每个用户发起5次请求,然后统计所有访问花费的时间。
6.3 测试结果
使用缓存后的测试结果 不使用缓存时的测试结果
所有请求花费的总时间(毫秒) 20569 22870
性能测试的详细结果请大家查看下载内容中的《不使用cache时的系统性能测试结果.txt》和《使用cache后系统性能测试结果.txt》
7 总结
在J2EE系统中,我们经常需要处理一些特殊的动态内容,这些内容在一个时间段内的变更非常有限,但是又不得不将他们确定为动态内容进行输出,而且非常消耗数据库系统资源或者web服务器的资源,这时我们就可以采用Cache----一种用于提高系统响应速度、改善系统运行性能的技术----来优化我们的系统。尤其是在Web应用中,这种处理可以很显著的改善系统运行性能。
本文中作者给大家介绍一个实现J2EE框架中Web应用层缓存功能的开放源代码项目----OSCache。它提供了在J2EE系统中实现缓存需要的丰富的功能。通过应用OSCache,我们不但可以实现通常的Cache功能、自由的设定cache的相关特性比如缓存时间段/缓存内容等,提升系统性能,而且还能有效的改善系统的稳定性。除此之外,OSCache组件还提供了更多的特性比如集群、容错、灵活的缓存区选择等。
作者根据自己的使用经验给大家提供了一些简单的例子,他们部分演示了如何使用OSCache组件提供的丰富特性,OSCache提供的特性远不止这些,需要大家在今后的时间里深入的研究,同时也希望大家通过E-mail和作者贡献研究成果。
--源码分析
oscache的代码量很少。分析源码的时候,主要分析com.opensymphony.oscache.base.Cache类就可以,这个又是整个oscache的核核心类,除此之外,都是辅助类。oscache用很少的代码量来实现这么强大的功能,应该说代码还是很值得阅读的。
com.opensymphony.oscache.base.Cache的api中主要的方法如下:
public void putInCache(String key, Object content, String[] groups, EntryRefreshPolicy policy, String origin);
public void getFromCache(String, int, String);
public void cancelUpdate(String key);
如何使用,引用官方的docs中的一段例子如下:
String myKey = "myKey";
String myValue;
int myRefreshPeriod = 1000;
try {
// Get from the cache
myValue = (String) admin.getFromCache(myKey, myRefreshPeriod);
} catch (NeedsRefreshException nre) {
try {
// Get the value (probably from the database)
myValue = "This is the content retrieved.";
// Store in the cache
admin.putInCache(myKey, myValue);
} catch (Exception ex) {
// We have the current content if we want fail-over.
myValue = (String) nre.getCacheContent();
// It is essential that cancelUpdate is called if the
// cached content is not rebuilt
admin.cancelUpdate(myKey);
}
}
这段代码是非常使用oscache的经典代码。细说如下myValue = (String) admin.getFromCache(myKey, myRefreshPeriod);从对象缓存map中取出myKey对应的对象,两种情况可能发生: 一:如果myKey对应的对象存在(先前putInCache)并且没有过期(isStale()方法)那么getFromCache方法会正常返回。二:如果对应的对象不存在或者过期,又分为两种情况: 1)请求的线程第一个探测到对象过期,那么这个时候oscache会抛给client一个NeedRefreshException,
提示client,需要对数据进行一下刷新,怎么刷新,putInCache即可。 2)如果请求的线程并非第一个探测到对象过期,两种情况:I如果oscache.properties中对blocking设置为true,那么该线程会在此阻塞,直到putInCache在另一个线程中被调用或者是cancelUpdate被调用。II否则情况跟1)类型,也会出现NeedRefreshException.
上面的那个例子之所以要在catch(NeedRefreshException)中进行putInCache,又之所以要在putInCache的catch(Exception)中做cancelUpdate道理就在此,我们不希望看到一个线程在getFromCache的位置一直阻塞下去。
补充:oscache对一个cacheEntry是否是Stale(或者说expire)的判断原则:
1。Cache.flushEntry()
2。Cache.flushAll()
3。CacheEntry.setGroups(groups); Cache.flushGroup(group)
4。createTime + refreshPeriod< now
5。cronExpiry< now
6。CacheEntry自带的EntryRefreshPolish的needRefresh方法返回true
上面的6条中的任何一条如果为true, 那么CacheEntry就是stale的,need refresh!!