javaEE 使用ServletContext实现服务器端简单定时更新缓存

来源:互联网 发布:清华大学软件专业课程 编辑:程序博客网 时间:2024/05/22 23:12

我在做一个门户系统的时候遇到webService的性能问题,当时由于设计中webService传递的数据是非结构化的,因此需要建立大量的链接获取数据。后期测试时webService访问很慢,大概要7秒钟才能完成一个页面的数据。当时不想再更改webService服务器以及客户端代码了,就想着实现一个缓存,用户访问门户页面的时候,不是直接访问webService来获取数据,而是直接从缓存中查找,然后每5分钟调用webService更新一下门户系统的缓存,这样来优化页面的响应时间。

首先要注册一个ServletContextListener,这个监听器有两个方法(contextInitialized,contextDestroyed)分别是web应用启动和销毁的时候调用的。

在web应用启动的时候,调用webService,获取初始数据,放在ServletConetxt中

[java] view plaincopy
  1. package com.leec.yetsoon.listener;  
  2.   
  3. import java.util.Date;  
  4. import java.util.Map;  
  5.   
  6. import javax.servlet.ServletContext;  
  7. import javax.servlet.ServletContextEvent;  
  8. import javax.servlet.ServletContextListener;  
  9.   
  10. public class CacheListenter implements ServletContextListener{  
  11.   
  12.     public void contextDestroyed(ServletContextEvent event) {  
  13.         System.out.println("contextDestroyed");  
  14.           
  15.     }  
  16.   
  17.     public void contextInitialized(ServletContextEvent event) {  
  18.         //查询数据库获得所要共享的信息,获取需要缓存的信息,以map形式保存  
  19.         Map<String, Object> cacheMap=CacheMapFactory.getCacheMap();  
  20.           
  21.         //获得ServletContext实例     
  22.         ServletContext context = event.getServletContext();     
  23.         //将查询到的共享信息保存到ServletContext中 context.setAttribute();    
  24.         context.setAttribute("cacheMap", cacheMap);  
  25.         //将更新时间加入,以便实现定时刷新  
  26.         context.setAttribute("preDate"new Date());     
  27.         context.setAttribute("isRefreshing"false);     
  28.           
  29.     }  
  30.   
  31. }  
其次,还要再注册一个ServletRequestListener,监听每次客户端请求,当请求到来的时间比缓存中的上次更新时间超过5分钟的时候,调用webService,更新缓存。

[java] view plaincopy
  1. package com.leec.yetsoon.listener;  
  2.   
  3. import java.util.Date;  
  4. import java.util.Map;  
  5. import javax.servlet.ServletContext;  
  6. import javax.servlet.ServletRequestEvent;  
  7. import javax.servlet.ServletRequestListener;  
  8.   
  9. public class TimeCountListener implements  ServletRequestListener {  
  10.       
  11.     
  12.     private final static float CACHE_MAX_AGE = 5 * 60 * 1000;//定时5分钟    
  13.   
  14.     public void requestDestroyed(ServletRequestEvent arg0) {  
  15.           
  16.     }  
  17.   
  18.     public void requestInitialized(ServletRequestEvent event) {  
  19.         ServletContext context = event.getServletContext();     
  20.         if(!(Boolean)context.getAttribute("isRefreshing")      
  21.                 && ((new Date()).getTime() - ((Date)context.getAttribute("preDate")).getTime()) > CACHE_MAX_AGE){     
  22.         context.setAttribute("isRefreshing"true);     
  23.         //在这里再次查询数据库,并将ServletContext中的信息更新     
  24.         Map<String, Object> cacheMap=CacheMapFactory.getCacheMap();  
  25. <span style="white-space:pre">      </span>context.setAttribute("cacheMap", cacheMap);  
  26.         context.setAttribute("preDate"new Date());//每次更新缓存的同时也更新时间  
  27.         context.setAttribute("isRefreshing"false);     
  28.         }     
  29.           
  30.     }  
  31.   
  32. }  

这样就不用每次都消耗大量资源访问webService了~

这个缓存还存在一些问题,就是某个用户请求页面的时候,监听器接收到请求,并且满足

[java] view plaincopy
  1. !(Boolean)context.getAttribute("isRefreshing")      
  2.                 && ((new Date()).getTime() - ((Date)context.getAttribute("preDate")).getTime()) > CACHE_MAX_AGE  

条件时,进行缓存更新,这个过程是同步的,只有等待更新完毕,页面才能显示出来,这样对某些运气不好的个别客户端来讲,这个页面响应的时间是不可忍受的。

因此可以把更新缓存的动作改成异步的。以下代码没有进行过测试:

[java] view plaincopy
  1. package com.leec.yetsoon.listener;  
  2.   
  3. import java.util.Date;  
  4. import java.util.Map;  
  5. import javax.servlet.ServletContext;  
  6. import javax.servlet.ServletRequestEvent;  
  7. import javax.servlet.ServletRequestListener;  
  8.   
  9. public class TimeCountListener implements  ServletRequestListener {  
  10.       
  11.     
  12.     private final static float CACHE_MAX_AGE = 5 * 60 * 1000;//定时5分钟    
  13.   
  14.     public void requestDestroyed(ServletRequestEvent arg0) {  
  15.           
  16.     }  
  17.   
  18.     public void requestInitialized(ServletRequestEvent event) {  
  19.         final ServletContext context = event.getServletContext();     
  20.         if(!(Boolean)context.getAttribute("isRefreshing")      
  21.                 && ((new Date()).getTime() - ((Date)context.getAttribute("preDate")).getTime()) > CACHE_MAX_AGE){     
  22.         context.setAttribute("isRefreshing"true);     
  23.         //在这里再次查询数据库,并将ServletContext中的信息更新  
  24.         Thread t = new Thread(new Runnable(){  
  25.             public void run(){  
  26.                 Map<String, Object> cacheMap=CacheMapFactory.getCacheMap();  
  27.                 context.setAttribute("cacheMap", cacheMap);  
  28.             }  
  29.         });     
  30.         t.start();  
  31.         context.setAttribute("preDate"new Date());  
  32.         context.setAttribute("isRefreshing"false);     
  33.         }     
  34.           
  35.     }  
  36.   
  37. }  

另外一个问题,我在context中保存了一个状态--isRefreshing,每次在更新前
[java] view plaincopy
  1. context.setAttribute("isRefreshing"true);  

把状态设为正在更新,更新完毕之后,把状态再修改回去

context.setAttribute("isRefreshing", false);

每次更新的时候是要检查这个状态的,如果是正在更新,就不会再次更新,但是setAttribute的操作不是原子的,因此也可能有多个用户进入到更新缓存的状态,这个进入的会不会经常发生也没有在生产条件下测试过,因此上面的这个缓存并发性很弱,能不能应用到生产环境很难保证~

0 0