首页 > 代码库 > 用缓冲技术提高JSP应用的性能和稳定性之OSchche应用(3)

用缓冲技术提高JSP应用的性能和稳定性之OSchche应用(3)

Oscache

特点

缓存任何对象,你可以不受限制的缓存部分jsp页面或HTTP请求,任何java对象都可以缓存。

拥有全面的API--OSCache API给你全面的程序来控制所有的OSCache特性。

永久缓存--缓存能随意的写入硬盘,因此允许昂贵的创建(expensive-to-create)数据来保持缓存,甚至能让应用重启。

支持集群--集群缓存数据能被单个的进行参数配置,不需要修改代码。

缓存记录的过期--你可以有最大限度的控制缓存对象的过期,包括可插入式的刷新策略(如果默认性能不需要时)。

 

上一篇讲到如何配置oschche下面介绍如何应用:

1 建立一个**Bean的缓存类**Cache.
 2 import com.opensymphony.oscache.general.*;import com.opensymphony.oscache.base.*;
 3 建立一个**DAO类以便操作数据库(也可将上两项的内容直接合并到这里).
4 在**Cache类里生成一个GeneralCacheAdministrator的实例admin用来管理缓存.
   GeneralCacheAdministrator admin = new GeneralCacheAdministrator();
 5 **Cache中的方法 如.clear(),flush().基本调用的为OSCache的 Java API.
 6 在**Cache添加getBeans()和getBean()方法 以便获取缓存中的实例对象.具体实现可参照上面的例子.
7 主要用到的GeneralCacheAdministrator的方法有
public Object getFromCache(String key) throws NeedsRefreshException; -- 从缓存中获取一个key标识的对象.
public Object getFromCache(String key, int refreshPeriod) throws NeedsRefreshException ; -- 从缓存中获取一个key标识的对象.  refreshPeriod刷新周期,标识此对象在缓存中保存的时间(单位:秒)

 注意:
如果一个NeedsRefreshException出现 必须调用admin.putInCache或admin.cancelUpdate来避免死锁情况发生.

 


OSCache的使用主要有4种:
1.POJO 缓存
2.HTTP Response 缓存
3.JSP Tag Library 缓存
4.O/R Data Access 缓存

 

1、POJO 缓存
这种方式的缓存直接调用OSCache的API进行,主要用于处理页面内容会根据参数动态改变,可以将参数设置为key值来保存数据:
首先,声明成员变量:
 // OSCache Adminitrator instance
 private static GeneralCacheAdministrator cacheAdmin = null;
其次,进行初始化:
 public RingArtistAction() {
  cacheAdmin = new GeneralCacheAdministrator();
 }
将POJO进行缓存:
  // Cache data key and refresh period
  String key = sex + ":" + place;
  int refreshPeriod = Constants.getIntegerValue(Constants.OSCACHE_REFRESH_PERIOD).intValue();
  try {
      // Get from the cache
   artists = (Map) cacheAdmin.getFromCache(key, refreshPeriod);
  } catch (NeedsRefreshException nre) {
      try {
          // Get the value (probably from the database)
    int count = getArtistCount(sex, place, errors);
    artists = getArtistData(sex, place, count, errors);
          // Store in the cache
    cacheAdmin.putInCache(key, artists);
      } catch (Exception ex) {
          // We have the current content if we want fail-over.
    artists = (Map) nre.getCacheContent();
          // It is essential that cancelUpdate is called if the
          // cached content is not rebuilt
    cacheAdmin.cancelUpdate(key);
    ex.printStackTrace();
      }
  }
POJO(简单Java对象)缓存。一个POJO缓存是一个系统——它担当一个"面向对象的"分布式的缓存。在这个系统中,一旦一个用户把POJO依附到该缓存上,那么缓冲方面(例如复制和持续性)应该对用户是透明的。一个用户只需简单地在该POJO上操作而不须担心更新该缓存内容或维持对象关系的问题。不存在显式的API调用可用来管理该缓存。
 
2、HTTP Response 缓存
这种方式的缓存用来处理整个页面的内容固定,不会根据参数动态改变:
首先在web.xml中配置CacheFilter:
 <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>86400</param-value>
  </init-param>
  <init-param>
   <param-name>scope</param-name>
   <param-value>application</param-value>
  </init-param>
 </filter>
将所有需要缓存的页面加入filter-mapping:
 <filter-mapping>
  <filter-name>Set Character Encoding</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>
注意,只有返回状态为200(HttpServletResponse.SC_OK)的内容才会被缓存

HTTP中缓存的目的是为了在很多情况下减少发送请求,也即直接返回缓存;同时在许多情况下可以不需要发送完整响应。前者减少了网络回路的数量,挺高响应速度,HTTP利用一个“过期(expiration)”机制来为此目的。后者减少了网络应用的带宽,HTTP用“验证(validation)”机制来为此目的。

HTTP定义了3种缓存机制:

l Freshness allows a response to be used without re-checking it on the origin server, and can be controlled by both the server and the client. For example, the Expires response header gives a date when the document becomes stale, and the Cache-Control: max-age directive tells the cache how many seconds the response is fresh for.

l Validation can be used to check whether a cached response is still good after it becomes stale. For example, if the response has a Last-Modified header, a cache can make a conditional request using the If-Modified-Since header to see if it has changed.

l Invalidation is usually a side effect of another request that passes through the cache. For example, if URL associated with a cached response subsequently gets a POST, PUT or DELETE request, the cached response will be invalidated.

HTTP Response缓存如何工作

所有的缓存都用一套规则来帮助他们决定什么时候使用缓存中的副本提供服务(假设有副本可用的情况下);一些规则在协议中有定义(HTTP协议1.0和1.1),一些规则由缓存的管理员设置(浏览器的用户或者代理服务器的管理员);
一般说来:遵循以下基本的规则(不必担心,你不必知道所有的细节,细节将随后说明)
1.如果响应头信息:告诉缓存器不要保留缓存,缓存器就不会缓存相应内容;
2.如果请求信息是需要认证或者安全加密的,相应内容也不会被缓存;
3.如果在回应中不存在校验器(ETag或者Last-Modified头信息),缓存服务器会认为缺乏直接的更新度信息,内容将会被认为不可缓存。
4.一个缓存的副本如果含有以下信息:内容将会被认为是足够新的 ?含有完整的过期时间和寿命控制头信息,并且内容仍在保鲜期内;
?浏览器已经使用过缓存副本,并且在一个会话中已经检查过内容的新鲜度;
?缓存代理服务器近期内已经使用过缓存副本,并且内容的最后更新时间在上次使用期之前;
?够新的副本将直接从缓存中送出,而不会向源服务器发送请求;

5.如果缓存的副本已经太旧了,缓存服务器将向源服务器发出请求校验请求,用于确定是否可以继续使用当前拷贝继续服务;

总之:新鲜度和校验是确定内容是否可用的最重要途径:

如果副本足够新,从缓存中提取就立刻能用了;
而经缓存器校验后发现副本的原件没有变化,系统也会避免将副本内容从源服务器整个重新传输一遍。


3、JSP Tag 缓存
JSP Tag缓存主要用于缓存JSP页面的局部内容:
  <cache:cache key="especialcategory" cron="* 5 * * *">
  <jsp:include page="/ringcategory.do" flush="true" >
    <jsp:param name="ringType" value=http://www.mamicode.com/"1"/>
  </jsp:include>
  </cache:cache>

页面缓存通常采用oscache来进行实现,oscache提供了一个jsp tag,可通过这个tag来包含需要缓存的内容部分,当然,缓存的这个内容部分需要有对服务器的请求或逻辑计算等的,可想而知,去缓存一段静态html是没有意义的。页面的缓存的使用对于系统的响应速度确实会有很大的提升,在实现页面缓存时最麻烦的主要是缓存的key的定义以及缓存更新的通知,这个自然框架是没法解决的,不过缓存更新的通知其实在框架中可以考虑一种通知模型的,就像事件通知那样。在实际的项目中,可以实现一个这样的通知模型或者就是简单的采用单例方式来标识某个key是否需要更新。

关于jsp cache的几条建议
1.jsp cache最好做在过滤器上,把需要缓冲的页面集中在同一个目录下,每次更改只须更改web.xml就可以完成缓冲设置,这样比较方便.
2.Gzip压缩可以将页面压缩得很小,平均压缩比为1/3,jsp cache的HashMap缓冲压缩后的页面,肯定比没压缩前更节约内存消耗,并且效率更高.

 


4、O/R Data Access 缓存

数据缓存估计大家都很熟悉,就是对系统的数据进行缓存的方式,典型的就是Hibernate的一级、二级数据缓存。
数据缓存在实现上如果是用hibernate的话更多的是直接使用hibernate的一级、二级以及查询缓存,如果要实现的话可以去参考hibernate的实现机制。
数据缓存的key在一级、二级缓存中采用的都是数据的标识键的值的方式,查询缓存采用的是查询参数、查询语句的方式。
数据缓存的更新则是hibernate在进行存储时直接更新缓存的内容,而对于查询缓存则是采用全部直接清除的方式,这样在下次进行查询时自然会重新去查询,
大家可能会想,为什么页面缓存和处理缓存不采用这样的方式来实现缓存的更新,稍微想想就知道了,在后台发生改变的时候其实是不知道需要移除哪些key的,
所以hibernate为了避免这个麻烦,采用的就是当数据一旦发生改变的时候就清除全部的查询缓存,而不是只去清除相关的缓存,
其实这里可以采用一种订阅式的模型,当然,也增加了框架的复杂度。

ORM缓存最强大的是它的透明化和灵活可配置,你可以使用Ehcache, 也可以选Jboss,还可以用Tangosol。

请阅读参考资料的内容获取详情。

参考资料:
Taking the load off: OSCache helps databases cope:http://www.theserverside.com/articles/article.tss?l=OSCacheHelpsDatabases

 


具体实现:

建立缓存管理类CacheManager
package com.jrgy.util;
public class CacheManager {
 private BaseCache newsCache;
 private static CacheManager instance; 
    private static Object lock = new Object();
   
   
    private CacheManager() { 
        // 初始BaseCache; 
        newsCache = new BaseCache("CNYJWeb", 12000); 
     }
   
    public static CacheManager getInstance() { 
        if (instance == null) { 
            synchronized (lock) { 
               if (instance == null) { 
                   instance = new CacheManager(); 
               } 
            } 
        } 
        return instance; 
     }
   
    //删除?所有缓存对象
    public void removeAllCache() {
        newsCache.removeAll();     
    }
   
    //删除被缓存的对象
    public void delCacheByKey(String key){
     newsCache.remove(key);
    }
   
    //添加被缓存的对象
    public void add(String key, Object value) {
     newsCache.put(key, value);
    }
   
    // 获取被缓存的对象
    public Object get(String key){
     try {
   return newsCache.get(key);
  } catch (Exception e) {
   return null;
  }
    }
   
}


 

建立个工具类BaseController  GeneralCacheAdministrator主要对实现持久化对象的保存以及取出的相关的操作。

package com.jrgy.util;
import java.util.Date;
import com.opensymphony.oscache.base.NeedsRefreshException;
import com.opensymphony.oscache.general.GeneralCacheAdministrator;

public class BaseCache extends GeneralCacheAdministrator {  
 
 private static final long serialVersionUID = -4397192926052141162L;
 
 private int refreshPeriod; // 过期时间(单位为秒);

 private String keyPrefix; // 关键字前??字符;


 public BaseCache(String keyPrefix, int refreshPeriod) {
  super();
  this.keyPrefix = keyPrefix;
  this.refreshPeriod = refreshPeriod;
 }

 // 添加被缓存的对象;
 public void put(String key, Object value) {
  this.putInCache(this.keyPrefix + "_" + key, value);
 }

 // 删除被缓存的对象;
 public void remove(String key) {
  this.flushEntry(this.keyPrefix + "_" + key);
 }

 // 根据日期删除?被缓存的对对象;
 public void removeAll(Date date) {
  this.flushAll(date);
 }

 //删除?所有的缓存对象?;
 public void removeAll() {
  this.flushAll();
 }

 // 获取被缓存的对象;
 public Object get(String key) throws Exception {
  try {
   return this.getFromCache(this.keyPrefix + "_" + key,
     this.refreshPeriod);
  } catch (NeedsRefreshException e) {
   this.cancelUpdate(this.keyPrefix + "_" + key);
   throw e;
  }
 }
}

使用oschache

package com.jrgy.web.controller;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.json.JSONArray;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import com.jrgy.main.service.impl.LoadStartDataManagerImpl;
import com.jrgy.pojo.domain.DocInfo;
import com.jrgy.pojo.domain.FriendlyLink;
import com.jrgy.pojo.domain.ItemInfo;
import com.jrgy.pojo.domain.Periodical;
import com.jrgy.util.Constants;
import com.jrgy.util.ItemCodeConstants;
import com.jrgy.util.PageObject;
import com.jrgy.util.PageView;
import com.jrgy.web.service.IWebIndexManager;

@Controller
public class WebIndexController extends BaseController {

 private static final long serialVersionUID = 6316819588600885728L;
 @Autowired
 IWebIndexManager webIndexManager;
 
 @Autowired
 LoadStartDataManagerImpl loadStartData;
 @SuppressWarnings("unchecked")
 @RequestMapping("/index.do")
 public ModelAndView saveDept(HttpServletRequest request,
   HttpServletResponse response) {
  PageObject pageObject = new PageObject();
  List<ItemInfo> itemList = new ArrayList<ItemInfo>();// 获取栏目
  
  if ((itemList = (List<ItemInfo>) getCache(itemList, ItemCodeConstants.CACHE_JRGY_ITEM)).size() == 0) {
   itemList = webIndexManager.addItemList(itmeCode);
   addCache(ItemCodeConstants.CACHE_JRGY_ITEM, itemList);
  }


 }

 

因为配置在D:\CNYJWeb\cache\application生成缓存的文件夹:

の

 


 

结论:
       1.缓存的清空与更新,要尽量精确的去操作受到更新影响的对象,而不是全部搞掉。
      在Hibernate当中,也提供了sessionFactory.evict(class, id)这样细粒度的清空缓存对象的方法。
          sessionFactory.evice(class)的操作,要看这样的操作是否频繁,如果频繁,对于缓存的作用就会大大的折扣。
   2.如果缓存对象过多,对于失效的算法与处理,要与业务对象的特性紧密的联合起来,通过事件来驱动对象的失效。
   3.对于商业对象的缓存,必须要深刻分析对象的生命周期,业务特性。
   4.对于数据不一致的风险,要有足够的认识与预防手段。
   5.合理的估计对象的大小,分配足够的内存
   6.如果只使用中心缓存,只能减小数据库的压力,对于网络带宽的压力,还是有的,速度上也远远逊于本地缓存的效果,所以要结合本地缓存+中心缓存的策略方案,即提高速度,避免群集复制时的瓶颈。