通过配置URL实现页面缓存的Fliter(修改配置无需重启)_JAVA_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > JAVA > 通过配置URL实现页面缓存的Fliter(修改配置无需重启)

通过配置URL实现页面缓存的Fliter(修改配置无需重启)

 2011/11/17 9:37:27  ooft  http://ooft.iteye.com  我要评论(0)
  • 摘要:在web系统中经常会有遇到一些公共页面,如首页,公告,动态生成jfreechart图片,这些页面访问量较大,每次访问会需要计算或者访问后台数据库,随着访问并发量的上升系统的压力会越来越大,这时就需要使用缓存来减小压力,提高性能.如一张首页面每次访问会访问一次数据库,当每秒并发为100时,每秒钟会访问100次数据库,如果对这个页面设置30秒的缓存,则每隔30才会访问一次数据库.数据库压力减小了3000倍.缓存分类:1.客户端缓存:即浏览器缓存
  • 标签:实现 配置 重启 URL
   在web系统中经常会有遇到一些公共页面,如首页,公告,动态生成jfreechart图片,这些页面访问量较大,每次访问会需要计算或者访问后台数据库,随着访问并发量的上升系统的压力会越来越大,这时就需要使用缓存来减小压力,提高性能.
  如一张首页面每次访问会访问一次数据库,当每秒并发为100时,每秒钟会访问100次数据库,如果对这个页面设置30秒的缓存,则每隔30才会访问一次数据库.数据库压力减小了3000倍.
  缓存分类:
1.客户端缓存: 即浏览器缓存,可以通过http的head头告诉浏览器该资源是否需要缓存,缓存多久.客户端缓存直接保存在本地,所以速度最快,而且对服务器没有访问压力.
2.CDN:全称是Content Delivery Network,即内容分发网络。其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。
比如sina,把中心服务器的内容分发到各个省市的cache服务器,根据用户所在的网络(ip段)来确定用户拉去最近服务器上的内容,达到访问速度最快。
3.前段缓存:主要用于缓存一些静态资源文件.代表性的有:(apache mod_cache、squid)
4.应用缓存:主要通过一些缓存框架对一些动态的数据进行缓存,以减少计算量或减少对数据库的访问次数.应用缓存又分为本地缓存和远程缓存.
     本地缓存是指缓存内容保存在本地jvm堆内存钟,访问速度极快,但不能跨越机器共享数据.典型的有OSCache,EHCache
     远程缓存是指缓存数据被集中保存在远端的缓存服务器上,应用服务器通过网络访问缓存数据.访问速度比本地缓存慢,但缓存内容不受应用服务器重启影响,多台应用服务器可以共享缓存数据.典型的有memcached
5.数据库缓存oracle,mysql等数据库系统都带有自己的缓存,将一些与编译的sql语句,热点数据进行缓存,提高访问速度.

碰到的问题
  我现在做的系统中有很多的页面报表和动态生成的统计图,这些资源一般1,2分钟更新一次就足够了,而且所有人看到的内容都是一样的.所以使用本地的应用缓存来对这些内容做缓存很划算.但这样做就需要修改代码,把缓存的api加进去(代码比较乱,用aop也比较麻烦).
  我首先想到的是oscache,这家伙是专门做页面级缓存的,专门实现了一个cachefilter来拦截页面请求.但研究发现oscache必须使用jsp标签来设置页面缓存的内容(我用的是velocity),而且配起来也比较麻烦.所以就想着自己实现一个简单的缓存过滤器.

设计说明
1.有一个配置文件可以登记那些url需要缓存,缓存多长时间,是否开启缓存.

2.配置文件修改以后立即生效,不需要重启.

3.只对get请求缓存,post不缓存.

4.缓存的key为请求的url(包括参数),不同的url缓存为不同的对象.

5.登记的url可以设置参数,当请求的url和登记url的参数匹配时才缓存.
如: 登记的url为
/a.htm?A=100&B=5
则请求的url缓存情况:
/a.htm  不缓存 
/a.htm?A=100  不缓存
/a.htm?A=100&B=5 缓存
/a.htm?A=100&B=5&C=3 缓存


编码
cacheUrl.properties配置文件登记需要缓存的url,缓存的时间和是否开启缓存.(用||分割)
/test1.jsp || 30 || true
/test2.jsp?param2=tom&param3=2010-11-11 || 60 ||true
/tt/test3.jsp?param1=100 || 30 ||true
/index.jsp || 60 || false

RefreshFileRead 动态读取文件里的数据,返回List<String>,一行一个String.
根据配置文件的最后修改时间来判断文件是否改变,一旦改变就重新读取文件中的数据.
/**
 * 动态读取配置文件类
 * @author dick_wyq
 * @version 1.0
 */
public class RefreshFileRead {
	private static final Log log = LogFactory.getLog(RefreshFileRead.class);
	/**
	 * 属性文件全名
	 */
	private String pfile = "cacheUrl.properties";

	/**
	 * 属性文件所对应的属性对象变量
	 */
	private long m_lastModifiedTime = 0;
	
	/**
	 * 上次检查时间
	 */
	private long lastCheckTime = 0;

	private List<String> lines;
	
	/**
	 * 动态文件的路径,classpath的相对路径.
	 * @param filepath
	 */
	public RefreshFileRead(String filepath) {
		if(filepath!=null)
			this.pfile=filepath;
		_RefreshFileRead();
	}	
	private void _RefreshFileRead() {
		m_lastModifiedTime = getFile().lastModified();
		if (m_lastModifiedTime == 0) {
			log.error(pfile + "file does not exist!");
		}		
	}
	/**
	 * 查找ClassPath路径获取文件
	 * 
	 * @return File对象
	 * @throws URISyntaxException
	 */
	private File getFile(){
		File m_file = new File("");
		try {
			URI fileUri = this.getClass().getClassLoader().getResource(pfile).toURI();
			m_file = new File(fileUri);
			
		} catch (URISyntaxException e) {
			log.error(pfile+"文件路径不正确");
		} catch (Exception e) {
			log.error(pfile+"文件读取异常");
		}
		return m_file;
	}

	/**
	 * 读取一特定的属性项
	 */
	public List<String> getLines() {
		if(this.lines==null||isFileModified()){
			//获取数据
			this.lines=_getLines();
		}
		return this.lines;
	}
	
	/**
	 * 判读文件是否改变
	 * @return
	 */
	private boolean isFileModified(){
		long nowCheckTime=System.currentTimeMillis();
		boolean result=false;
		//如果没到1秒钟,直接返回false
		if(nowCheckTime-lastCheckTime>5000){	
			lastCheckTime=nowCheckTime;
			long newTime = getFile().lastModified();
			
			// 检查属性文件是否被修改
			if (newTime == 0) {
				// 属性文件不存在
				if (m_lastModifiedTime == 0) {
					log.error(pfile + " file does not exist!");
				} else {
					log.error(pfile + " file was deleted!!");
				}
			}
			else if (newTime !=  m_lastModifiedTime){
				result= true;
				m_lastModifiedTime=newTime;
			}
		}
		
		
		return result;
	}
	/**
	 * 读取文件的数据到list里
	 * @return
	 */
	private List<String> _getLines(){
		List<String> myline=new ArrayList<String>();
		BufferedReader br=null;
		try {
			String l=null;
			br=new BufferedReader(new FileReader(getFile()));
			while((l=br.readLine())!=null){
				if(!l.trim().equals(""))
					myline.add(l);
			}
		} catch (Exception e) {
			log.error("read File error!,文件读取异常");
		}finally{
			if(br!=null)
				try {br.close();} catch (Exception e) {}
		}
		return myline;
	}
}


MyCacheFilter过滤器用来拦截缓存请求.当符合缓存条件的请求经过该过滤器时将会被拦截,并尝试从oscache缓存中获取数据,如果获取不成功,再继续原来得路径,并将返回的response对象保存的oscache中. filter的代码参考了oscache中得cachefilter的代码.
public class MyCacheFilter implements Filter {
 ...
 public void init(FilterConfig filterConfig) {        
        config = filterConfig;
        log.info("OSCache: Initializing CacheFilter with filter name " + config.getFilterName());        
        //读取配置文件的路径
        String cacheUrlFile = config.getInitParameter("cacheUrlFile");
        //实例化动态读取配置文件类,默认是cacheUrl.properties
        refreshFileRead=new RefreshFileRead(cacheUrlFile);
        //oscache实例
        cacheAdmin=new GeneralCacheAdministrator();
     }

   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
    	long l1=System.currentTimeMillis();
    	HttpServletRequest httpRequest = (HttpServletRequest) request;
    	UrlCheck urlCheck=new UrlCheck(refreshFileRead);
    	//判断当前请求是否在需要缓存的列表中
    	CacheUrlVo vo=urlCheck.checkReqCache(httpRequest);
    	if(vo==null){
    		//如果当前不请求符合缓存要求,就继续执行.
    		chain.doFilter(request, response);
            return;
    	}
    	//如果当前请求符合缓存要求.
    	//生成缓存的key
    	String key=genCacheKey(httpRequest);
    	//key太长,用md5编码
    	String md5key=Md5Utils.MD5(key);
    	//获取当前请求可以缓存的时间.
    	int cacheTime=vo.getCacheTime();
    	
    	try {
    		//直接从缓存中获取数据
            ResponseContent respContent = (ResponseContent) cacheAdmin.getFromCache(md5key, cacheTime);
            
            boolean acceptsGZip = false;
           //将缓存中获取的数据写入response中
            respContent.writeTo(response, false, acceptsGZip);
            
            if (log.isInfoEnabled()) {
                log.info("OK!!CACHE HITED"+",TIME="+(System.currentTimeMillis()-l1)+"ms," + key);
            }
    	} catch (NeedsRefreshException nre) {
            boolean updateSucceeded = false;

            try {
            	//包装response
                CacheHttpServletResponseWrapper cacheResponse = new CacheHttpServletResponseWrapper((HttpServletResponse) response, false, cacheTime * 1000L, lastModified, expires, cacheControlMaxAge);
                //继续执行
                chain.doFilter(request, cacheResponse);
                cacheResponse.flushBuffer();           
                //保存到cache中
                cacheAdmin.putInCache(md5key, cacheResponse.getContent());
                updateSucceeded = true;
                if (log.isInfoEnabled()) {
                    log.info("MMD!!CACHE NOT FIND,EXECUTE!"+",cacheTime="+vo.getCacheTime()+"second,TIME="+(System.currentTimeMillis()-l1)+"ms," + key);
                }                
            } finally {
                if (!updateSucceeded) {
                	cacheAdmin.cancelUpdate(md5key); //如果执行失败就取消更新
                }
            }
        }
    }
 ...
}


web.xml配置过滤器
<filter>
	<filter-name>MyCacheFilter</filter-name>
	<filter-class>cache.MyCacheFilter</filter-class>
</filter>
<filter-mapping>
	<filter-name>MyCacheFilter</filter-name>
         <url-pattern>*.jsp</url-pattern>
</filter-mapping>


执行结果:
第一次执行,没有缓存,执行时间375毫秒






第二次执行,有缓存,执行时间0毫秒



源代码:
附件包含了一个带有全部源代码和例子myeclipse工程,解压导入即可运行.
测试例子的url为:
http://127.0.0.1:8080/cacheWeb/test1.jsp
http://127.0.0.1:8080/cacheWeb/test2.jsp?param1=123&param2=tom&param3=2010-11-11
  • 描述: 第一次执行
  • 大小: 13.9 KB
  • 描述: 返回结果
  • 大小: 21.6 KB
  • 描述: 第二次执行
  • 大小: 10.5 KB
  • cacheWeb.zip (765.4 KB)
  • 描述: 源代码
  • 下载次数: 2
  • 查看图片附件
发表评论
用户名: 匿名