我在做一个门户系统的时候遇到webService的性能问题,当时由于设计中webService传递的数据是非结构化的,因此需要建立大量的链接获取数据。后期测试时webService访问很慢,大概要7秒钟才能完成一个页面的数据。当时不想再更改webService服务器以及客户端代码了,就想着实现一个缓存,用户访问门户页面的时候,不是直接访问webService来获取数据,而是直接从缓存中查找,然后每5分钟调用webService更新一下门户系统的缓存,这样来优化页面的响应时间。
首先要注册一个ServletContextListener,这个监听器有两个方法(contextInitialized,contextDestroyed)分别是web应用启动和销毁的时候调用的。
在web应用启动的时候,调用webService,获取初始数据,放在ServletConetxt中
- package com.leec.yetsoon.listener;
- import java.util.Date;
- import java.util.Map;
- import javax.servlet.ServletContext;
- import javax.servlet.ServletContextEvent;
- import javax.servlet.ServletContextListener;
- public class CacheListenter implements ServletContextListener{
- public void contextDestroyed(ServletContextEvent event) {
- System.out.println("contextDestroyed");
- }
- public void contextInitialized(ServletContextEvent event) {
- //查询数据库获得所要共享的信息,获取需要缓存的信息,以map形式保存
- Map<String, Object> cacheMap=CacheMapFactory.getCacheMap();
- //获得ServletContext实例
- ServletContext context = event.getServletContext();
- //将查询到的共享信息保存到ServletContext中 context.setAttribute();
- context.setAttribute("cacheMap", cacheMap);
- //将更新时间加入,以便实现定时刷新
- context.setAttribute("preDate", new Date());
- context.setAttribute("isRefreshing", false);
- }
- }
- package com.leec.yetsoon.listener;
- import java.util.Date;
- import java.util.Map;
- import javax.servlet.ServletContext;
- import javax.servlet.ServletRequestEvent;
- import javax.servlet.ServletRequestListener;
- public class TimeCountListener implements ServletRequestListener {
- private final static float CACHE_MAX_AGE = 5 * 60 * 1000;//定时5分钟
- public void requestDestroyed(ServletRequestEvent arg0) {
- }
- public void requestInitialized(ServletRequestEvent event) {
- ServletContext context = event.getServletContext();
- if(!(Boolean)context.getAttribute("isRefreshing")
- && ((new Date()).getTime() - ((Date)context.getAttribute("preDate")).getTime()) > CACHE_MAX_AGE){
- context.setAttribute("isRefreshing", true);
- //在这里再次查询数据库,并将ServletContext中的信息更新
- Map<String, Object> cacheMap=CacheMapFactory.getCacheMap();
- <span style="white-space:pre"> </span>context.setAttribute("cacheMap", cacheMap);
- context.setAttribute("preDate", new Date());//每次更新缓存的同时也更新时间
- context.setAttribute("isRefreshing", false);
- }
- }
- }
这样就不用每次都消耗大量资源访问webService了~
这个缓存还存在一些问题,就是某个用户请求页面的时候,监听器接收到请求,并且满足
- !(Boolean)context.getAttribute("isRefreshing")
- && ((new Date()).getTime() - ((Date)context.getAttribute("preDate")).getTime()) > CACHE_MAX_AGE
条件时,进行缓存更新,这个过程是同步的,只有等待更新完毕,页面才能显示出来,这样对某些运气不好的个别客户端来讲,这个页面响应的时间是不可忍受的。
因此可以把更新缓存的动作改成异步的。以下代码没有进行过测试:
- package com.leec.yetsoon.listener;
- import java.util.Date;
- import java.util.Map;
- import javax.servlet.ServletContext;
- import javax.servlet.ServletRequestEvent;
- import javax.servlet.ServletRequestListener;
- public class TimeCountListener implements ServletRequestListener {
- private final static float CACHE_MAX_AGE = 5 * 60 * 1000;//定时5分钟
- public void requestDestroyed(ServletRequestEvent arg0) {
- }
- public void requestInitialized(ServletRequestEvent event) {
- final ServletContext context = event.getServletContext();
- if(!(Boolean)context.getAttribute("isRefreshing")
- && ((new Date()).getTime() - ((Date)context.getAttribute("preDate")).getTime()) > CACHE_MAX_AGE){
- context.setAttribute("isRefreshing", true);
- //在这里再次查询数据库,并将ServletContext中的信息更新
- Thread t = new Thread(new Runnable(){
- public void run(){
- Map<String, Object> cacheMap=CacheMapFactory.getCacheMap();
- context.setAttribute("cacheMap", cacheMap);
- }
- });
- t.start();
- context.setAttribute("preDate", new Date());
- context.setAttribute("isRefreshing", false);
- }
- }
- }
另外一个问题,我在context中保存了一个状态--isRefreshing,每次在更新前
- context.setAttribute("isRefreshing", true);
把状态设为正在更新,更新完毕之后,把状态再修改回去
context.setAttribute("isRefreshing", false);
每次更新的时候是要检查这个状态的,如果是正在更新,就不会再次更新,但是setAttribute的操作不是原子的,因此也可能有多个用户进入到更新缓存的状态,这个进入的会不会经常发生也没有在生产条件下测试过,因此上面的这个缓存并发性很弱,能不能应用到生产环境很难保证~