Java Web应用程序:如何实现缓存技术?

时间:2009-03-31 04:32:06

标签: java servlets caching java-ee

我正在开发一个Java Web应用程序,它通过从Web服务加载的大型XML配置文件来实现它的行为。由于在访问应用程序的特定部分之前实际上不需要这些文件,因此它们会被懒惰地加载。当需要其中一个文件时,会向Web服务发送查询以检索相应的文件。由于某些配置文件可能会被使用得更多,比其他配置文件更频繁,我想设置某种缓存(可能有1小时的到期时间),以避免一遍又一遍地请求同一个文件。

Web服务返回的文件对于所有会话中的所有用户都是相同的。我不使用JSP,JSF或任何其他花哨的框架,只使用普通的servlet。

我的问题是,在Java Web应用程序中实现这样一个全局静态缓存的最佳实践是什么?单例类是否合适,或者由于J2EE容器会有奇怪的行为吗?我应该通过JNDI在某处暴露某些东西吗?我该怎么做才能使我的缓存不会在集群环境中搞砸(每个集群服务器有一个缓存可以,但不是必需的)?

鉴于上述信息,将负责缓存的对象作为ServletContext属性放置是否正确?

注意:我不想在启动时加载所有这些并完成它,因为那样

1)。每当我的应用程序启动时,web服务都会过载 2)。我的应用程序运行时文件可能会更改,所以无论如何我都要重新查询它们 3)。我仍然需要一个全局可访问的缓存,所以我的问题仍然存在

更新:使用缓存代理(例如squid)可能是一个好主意,但是对webservice的每个请求都会在post Data中发送相当大的XML查询,每次都可能不同。只有Web应用程序才真正知道对Web服务的两个不同调用实际上是等效的。

感谢您的帮助

6 个答案:

答案 0 :(得分:37)

以下是使用EhCache进行缓存的示例。此代码在多个项目中用于实现临时缓存。

1)将缓存放在全局上下文中。 (不要忘记在WEB.XML中添加监听器。)

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;

public class InitializationListener implements ServletContextListener {    
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext ctx = sce.getServletContext();
        CacheManager singletonManager = CacheManager.create();
        Cache memoryOnlyCache = new Cache("dbCache", 100, false, true, 86400,86400);
        singletonManager.addCache(memoryOnlyCache);
        cache = singletonManager.getCache("dbCache");       
        ctx.setAttribute("dbCache", cache );           
    }
}

2)在需要时检索缓存实例。即来自servlet:

cache = (Cache) this.getContext().getAttribute("dbCache");

3)在执行昂贵的操作之前查询缓存。

        Element e = getCache().get(key);
        if (e != null) {
            result = e.getObjectValue(); // get object from cache
        } else {
            // Write code to create the object you need to cache, then store it in the cache.
            Element resultCacheElement = new Element(key, result);
            cache.put(resultCacheElement);

        }

4)另外,请不要忘记在适当的时候使缓存的对象无效。

您可以找到更多样本here

答案 1 :(得分:13)

您的问题包含几个单独的问题。让我们慢慢开始。 ServletContext是一个可以存储缓存句柄的好地方。但是你需要为每个服务器实例提供缓存。应该没问题。如果要在更广泛的范围内注册缓存,请考虑将其注册到JNDI。

缓存的问题。基本上,您是通过webservice检索xml。如果您通过HTTP访问此Web服务,您可以在您的旁边安装简单的HTTP代理服务器来处理xml的缓存。下一步是在某种本地对象缓存中缓存已解析的xml。每个服务器可以存在此缓存而没有任何问题。在第二种情况下,EHCache将做得很好。在这种情况下,处理链将像这样Client - http request -> servlet -> look into local cache - if not cached -> look into http proxy (xml files) -> do proxy job (http to webservice)

优点:

  • 每个服务器实例的本地缓存,仅包含来自请求的xmls
  • 的对象
  • 一个与我们的webapp在同一硬件上运行的http代理。
  • 可以在不为xml文件添加新的http代理的情况下扩展webapp。

缺点:

  • 下一级基础架构
  • +1失败点(http代理)
  • 更复杂的部署

更新:不要忘记始终将HTTP HEAD请求发送到代理中以确保缓存是最新的。

答案 2 :(得分:8)

选项#1:使用开源缓存库,例如EHCache

当有许多好的开源替代方案可以插入并开始使用时,不要实现自己的缓存。实现自己的缓存要比大多数人意识到的要复杂得多,如果你不确切知道自己在做什么,那么你就可以轻松地重新开发并解决一些非常棘手的问题。

我建议EHCache使用Apache许可证。您需要查看the EHCace code samples

选项#2:使用Squid

更容易解决您的问题的方法是使用Squid ...将Squid置于请求缓存数据的进程和发出请求的系统之间:http://www.squid-cache.org/

答案 3 :(得分:1)

在做了一些自我调查后,似乎最简单的方法来实现我所需要的(在问题中描述的要求和可接受的限制内),将我的缓存对象添加到Servlet上下文中,并查看它在需要的地方(或传递它)。

我只是从ServletContextListener实例化我的配置加载器,并且在contextInitialized()方法中,我只是使用ServletContext.setAttribute()将它存储到ServletContext中。然后使用request.getSession()。getServletContext()。getAttribute()。

从servlet本身查找它很容易。

我认为这是在不引入spring或任何其他依赖注入框架的情况下执行此操作的正确方法。

答案 4 :(得分:1)

Bref,你可以使用这个现成的弹簧ehcache配置

1- ehcache.xml :显示Ehcache的全局配置。

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="./ehcache.xsd" updateCheck="false" monitoring="autodetect" dynamicConfig="true" name="myCacheManager">

    <!-- 
    see ehcache-core-*.jar/ehcache-fallback.xml for description of elements
    Attention: most of those settings will be overwritten by hybris
     -->
    <diskStore path="java.io.tmpdir"/>

</ehcache>

2- ehcache-spring.xml :创建EhCacheManagerFactoryBean和EhCacheFactoryBean。

    <bean id="myCacheManager"  class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
        scope="singleton">
        <property name="configLocation" value="ehcache.xml" />
        <property name="shared" value="true" />

    </bean>

 <bean id="myCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean" scope="singleton">
        <property name="cacheManager" ref="myCacheManager" />
        <property name="cacheName" value="myCache" />
        <property name="maxElementsInMemory" value="1000" />
        <property name="maxElementsOnDisk" value="1000" />
        <property name="eternal" value="false" />
        <property name="diskPersistent" value="true" />
        <property name="timeToIdle" value="600" />
        <property name="timeToLive" value="1200" />
        <property name="memoryStoreEvictionPolicy" value="LRU" />
        <property name="statisticsEnabled" value="true" />
        <property name="sampledStatisticsEnabled" value="true" />
    </bean>

3-注入&#34; myCache&#34;您的业​​务类中的bean,请参阅以下示例,以开始获取并将对象放入缓存中。

@Resource("myCache")
private net.sf.ehcache.Cache myCache; 

@Resource("myService")
private Service myService; 

public byte[] getFromCache(final String code) 
{ 
// init Cache 
final StringBuilder builder = new StringBuilder(); 
 // key to identify a entry in cache map
final String key = code;
// get form the cache 
final Element element = myCache.get(key); 
if (element != null && element.getValue() != null) 
{ 
       return (byte[]) element.getValue(); 
} 

final byte[] somethingToBeCached = myService.getBy(code); 
// store in the cache
myCache.put(new Element(key, somethingToBeCached)); 

return somethingTobeCached; 

} 

答案 5 :(得分:0)

将缓存的对象实例放在ServletContext中没有任何问题。不要忘记使用此对象的setAttributes方法的其他2个选项(请求范围,会话范围)。 webcontainers和j2ee服务器本身支持的任何东西都是好的(好的,我的意思是它的供应商独立,没有像Spring这样的重型j2ee库)。我最大的要求是服务器在5-10秒内启动并运行。

我真的不喜欢所有的缓存解决方案,因为它很容易让它在本地机器上运行,并且很难让它在生产机器上运行。 EHCACHE,Infinispan等。除非您需要集群范围的复制/分发,与Java生态系统紧密集成,您可以使用REDIS(NOSQL数据库)或nodejs ......任何具有HTTP接口的东西都可以。特别是

缓存可以非常简单,这里是纯java解决方案(没有框架):

import java.util.*;

/*
  ExpirableObject.

  Abstract superclass for objects which will expire. 
  One interesting design choice is the decision to use
  the expected duration of the object, rather than the 
  absolute time at which it will expire. Doing things this 
  way is slightly easier on the client code this way 
  (often, the client code can simply pass in a predefined 
  constant, as is done here with DEFAULT_LIFETIME). 
*/

public abstract class ExpirableObject {
  public static final long FIFTEEN_MINUTES = 15 * 60 * 1000;
  public static final long DEFAULT_LIFETIME = FIFTEEN_MINUTES;

  protected abstract void expire();

  public ExpirableObject() {
    this(DEFAULT_LIFETIME);
  }

  public ExpirableObject(long timeToLive) {
    Expirer expirer = new Expirer(timeToLive);
    new Thread(expirer).start();
  }

  private class Expirer implements Runnable {  
    private long _timeToSleep;
    public Expirer (long timeToSleep){
      _timeToSleep = timeToSleep;
    }

    public void run() {
      long obituaryTime = System.currentTimeMillis() + _timeToSleep; 
      long timeLeft = _timeToSleep;
      while (timeLeft > 0) {
        try {
          timeLeft = obituaryTime - System.currentTimeMillis();  
          if (timeLeft > 0) {
            Thread.sleep(timeLeft);
          } 
        }
        catch (InterruptedException ignored){}      
      }
      expire();
    }
  }
}

请参阅此link以获得进一步改进。

相关问题