2009-03-31 19 views
22

Estoy desarrollando una aplicación web Java que basa su comportamiento a través de grandes archivos de configuración XML que se cargan desde un servicio web. Como estos archivos no son realmente necesarios hasta que se accede a una sección particular de la aplicación, se cargan de forma perezosa. Cuando se requiere uno de estos archivos, se envía una consulta al servicio web para recuperar el archivo correspondiente. Como es probable que algunos de los archivos de configuración se usen mucho, con mucha más frecuencia que otros, me gustaría configurar algún tipo de almacenamiento en caché (con un tiempo de caducidad de aproximadamente 1 hora) para evitar solicitar el mismo archivo una y otra vez.Aplicación web Java: ¿cómo implementar técnicas de almacenamiento en caché?

Los archivos devueltos por el servicio web son los mismos para todos los usuarios en todas las sesiones. No uso JSP, JSF o cualquier otro marco elegante, solo servlets simples.

Mi pregunta es, ¿qué se considera una mejor práctica para implementar un caché estático global en una aplicación web Java? ¿Es apropiada una clase Singleton, o habrá comportamientos extraños debido a los contenedores J2EE? ¿Debo exponer algo en algún lugar a través de JNDI? ¿Qué debo hacer para que mi caché no se atornille en entornos agrupados (está bien, pero no es necesario, tener un caché por servidor agrupado)?

Dada la información anterior, ¿sería una implementación correcta poner un objeto responsable del almacenamiento en caché como un atributo ServletContext?

Nota: No quiero cargar todos ellos en el arranque y hacerse con él porque eso

1). sobrecargar el servicio web cada vez que se inicie mi aplicación
2). Los archivos pueden cambiar mientras mi aplicación se está ejecutando, por lo que tendría que volver a consultarlos de todos modos
3). Todavía necesitaría un caché accesible globalmente, entonces mi pregunta aún es válida

Actualización: Usar un proxy de caché (como squid) puede ser una buena idea, pero cada solicitud al servicio web enviará consultas XML bastante grandes en la publicación Datos, que pueden ser diferentes cada vez. Solo la aplicación web realmente sabe que dos llamadas diferentes al servicio web son en realidad equivalentes.

Gracias por su ayuda

Respuesta

11

Su pregunta contiene varias preguntas separadas. Comencemos lentamente. ServletContext es un buen lugar donde puede almacenar el control de su caché. Pero pagas teniendo caché por instancia de servidor. No debería ser un problema. Si desea registrar el caché en un rango más amplio, considere registrarlo en JNDI.

El problema con el almacenamiento en caché. Básicamente, estás recuperando xml a través de webservice. Si está accediendo a este servicio web a través de HTTP, puede instalar un servidor proxy HTTP simple de su lado que maneje el almacenamiento en caché de xml. El siguiente paso será el almacenamiento en caché de xml resuelto en algún tipo de caché de objetos locales. Este caché puede existir por servidor sin ningún problema. En este segundo caso, EHCache hará un trabajo perfecto. En este caso, la cadena de procesamiento será así Client - http request -> servlet -> look into local cache - if not cached -> look into http proxy (xml files) -> do proxy job (http to webservice).

Pros:

  • caché local por cada instancia de servidor, que contiene sólo los objetos de XMLs solicitados
  • Uno proxy HTTP que se ejecutan en mismo hardware que nuestra aplicación web.
  • Posibilidad de escalar aplicaciones web sin agregar nuevos proxies http para archivos xml.

Contras:

  • siguiente nivel de infraestructura
  • 1 punto de fallo (proxy HTTP)
  • más complicado despliegue

Actualización: no se olvide de siempre envíe la solicitud HTTP HEAD al proxy para asegurarse de que el caché esté actualizado.

8

Opción # 1: Utilice una biblioteca de almacenamiento en caché de código abierto Tal como EHCache

No poner en práctica su propia caché cuando hay un buen número de alternativas de código abierto que se puede caer y comenzar a usar. La implementación de su propio caché es mucho más compleja de lo que la mayoría de la gente cree y si no sabe exactamente qué está haciendo al enrutar, fácilmente comenzará a reinventar la rueda y a resolver algunos problemas muy difíciles.

Recomendaría EHCache que está bajo una licencia de Apache. Deberá echar un vistazo al the EHCace code samples.

Opción # 2: Uso calamar

Una solución aún más fácil a su problema sería el uso de calamar ... Ponga calamar en el medio del proceso que solicita los datos a ser almacenados en caché y el sistema que realiza la solicitud : http://www.squid-cache.org/

+0

Gracias, pero eso realmente no responde mi pregunta. Si decido usar EHCache, ¿cómo lo configuro dentro de un contenedor java ee para que funcione correctamente en todas las sesiones, varios desorden de cargadores de clases y en entornos agrupados? – LordOfThePigs

+0

No estoy convencido de que Squid sea la solución correcta. Parece tremendamente ingenioso, sin embargo, no creo que los servicios web respeten "si se modifica" – monksy

1

Después de mirar un poco más a mi alrededor, parece que la forma más fácil de lograr lo que necesito (dentro de los requisitos y las limitaciones aceptables descritas en la pregunta) sería agregar mi objeto de caché al contexto de servlet. y buscarlo (o pasarlo) donde sea necesario.

Me gustaría crear una instancia de mi cargador de configuración de un ServletContextListener, y dentro del método contextInitialized(), simplemente lo almacenaría en el ServletContext usando ServletContext.setAttribute(). Entonces es fácil buscarlo desde los propios servlets usando request.getSession(). GetServletContext(). GetAttribute().

Supongo que esta es la manera correcta de hacerlo sin introducir resorte o cualquier otro marco de inyección de dependencia.

+0

¿Conocen el caché de guayabo? obtener o recuperar si no está configurado? https://code.google.com/p/guava-libraries/wiki/CachesExplained – maress

35

Aquí hay un ejemplo de almacenamiento en caché con EhCache. Este código se usa en varios proyectos para implementar el almacenamiento en caché ad hoc.

1) Ponga su caché en el contexto global. (No olvide agregar el oyente en 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) Recuperar la instancia de caché cuando lo necesite. es decir, desde un servlet:

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

3) Consultar el caché justo antes de que hagas una operación costosa.

 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) Tampoco se olvide de invalidar los objetos almacenados en caché cuando sea apropiado.

Puede encontrar más muestras here

1

Bref, puede utilizar esta configuración ehcache primavera listo

1- ehcache.xml: mostrar configuración global de 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: crear EhCacheManagerFactoryBean y 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- Inyectar "myCache" grano en su clase de negocios, consulte los siguientes exemple para empezar con conseguir y poner un objeto en la memoria caché.

@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; 

} 
0

No tuve ningún problema con poner instancia de objeto en caché dentro de ServletContext. No olvide otras 2 opciones (alcance de solicitud, alcance de sesión) con los métodos setAttributes de estos objetos. Todo lo que se admite de forma nativa dentro de los contenedores web y los servidores j2ee es bueno (me refiero a que es independiente del proveedor y sin pesadas bibliotecas j2ee como Spring). Mi mayor exigencia es que los servidores se pongan en funcionamiento en 5-10 segundos.

Realmente no me gusta toda la solución de almacenamiento en caché, porque es tan fácil hacer que funcione en la máquina local, y es difícil hacerlo funcionar en las máquinas de producción. EHCACHE, Infinispan, etc. A menos que necesite una replicación/distribución amplia, estrechamente integrada con el ecosistema de Java, puede usar REDIS (base de datos NOSQL) o nodejs ... Cualquier cosa con la interfaz HTTP servirá. Especialmente

almacenamiento en caché puede ser muy fácil, y aquí está la solución pura de Java (no hay marcos):

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(); 
    } 
    } 
} 

Por favor, se refieren a este link de mejoras adicionales.

Cuestiones relacionadas