wake-up-neo.net

Java Web Application: Wie implementiere ich Caching-Techniken?

Ich entwickle eine Java-Webanwendung, die auf großen XML-Konfigurationsdateien basiert, die von einem Webdienst geladen werden. Da diese Dateien erst benötigt werden, wenn auf einen bestimmten Abschnitt der Anwendung zugegriffen wird, werden sie träge geladen. Wenn eine dieser Dateien erforderlich ist, wird eine Abfrage an den Webservice gesendet, um die entsprechende Datei abzurufen. Da einige der Konfigurationsdateien wahrscheinlich viel häufiger verwendet werden als andere, möchte ich eine Art Zwischenspeicherung einrichten (mit einer Verfallszeit von 1 Stunde), um zu vermeiden, dass immer dieselbe Datei angefordert wird.

Die vom Webdienst zurückgegebenen Dateien sind für alle Benutzer in allen Sitzungen gleich. Ich verwende keine JSP, JSF oder ein anderes ausgefallenes Framework, sondern einfache Servlets.

Meine Frage lautet: Was ist die beste Vorgehensweise, um einen solchen globalen statischen Cache in einer Java-Webanwendung zu implementieren? Ist eine Singleton-Klasse angemessen, oder wird es aufgrund der J2EE-Container merkwürdige Verhaltensweisen geben? Soll ich irgendwo durch JNDI etwas herausstellen? Was soll ich tun, damit mein Cache in Clusterumgebungen nicht beschädigt wird (es ist in Ordnung, aber es ist nicht notwendig, einen Cache pro Clusterserver zu haben)? 

Wäre es angesichts der obigen Informationen eine korrekte Implementierung, ein Objekt, das für das Caching verantwortlich ist, als ServletContext-Attribut zu definieren?

Hinweis: Ich möchte nicht alle beim Start laden und damit fertig sein, weil dies der Fall wäre 

1). Überladen Sie den Webservice, wenn meine Anwendung gestartet wird
2). Die Dateien ändern sich möglicherweise, während meine Anwendung ausgeführt wird. Daher müsste ich sie trotzdem neu anfordern
3). Ich würde immer noch einen global zugänglichen Cache benötigen, daher ist meine Frage immer noch gültig

Update: Die Verwendung eines Caching-Proxys (z. B. Squid) ist möglicherweise eine gute Idee, aber jede Anforderung an den Webservice sendet ziemlich große XML-Abfragen in den Post-Daten, die sich jedes Mal unterscheiden können. Nur die Webanwendung weiß wirklich, dass zwei verschiedene Aufrufe des Webservice tatsächlich gleichwertig sind.

Danke für Ihre Hilfe

23
LordOfThePigs

Ihre Frage enthält mehrere separate Fragen. Beginnen wir langsam. ServletContext ist ein guter Ort, an dem Sie den Cache speichern können. Sie zahlen jedoch mit einem Cache pro Serverinstanz. Es sollte kein Problem sein. Wenn Sie den Cache in einem größeren Bereich registrieren möchten, sollten Sie ihn in JNDI registrieren.

Das Problem mit dem Caching. Grundsätzlich rufen Sie XML über den Webservice ab. Wenn Sie über HTTP auf diesen Webservice zugreifen, können Sie auf Ihrer Seite einen einfachen HTTP-Proxyserver installieren, der das Zwischenspeichern von XML übernimmt. Der nächste Schritt ist das Zwischenspeichern von aufgelösten XML-Dateien in einer Art lokalem Objektcache. Dieser Cache kann problemlos pro Server existieren. In diesem zweiten Fall erledigt der EHCache perfekte Arbeit. In diesem Fall ist die Verarbeitungskette wie folgt: Client - http request -> servlet -> look into local cache - if not cached -> look into http proxy (xml files) -> do proxy job (http to webservice)

Pros:

  • Lokaler Cache pro Serverinstanz, die nur Objekte aus angeforderten XML-Dateien enthält
  • Ein http-Proxy, der auf derselben Hardware wie unsere Webapp ausgeführt wird.
  • Möglichkeit zur Skalierung von WebApp ohne Hinzufügen neuer HTTP-Proxys für XML-Dateien.

Nachteile:

  • Nächste Infrastrukturebene
  • +1 Ausfallpunkt (http-Proxy)
  • Kompliziertere Bereitstellung

Update: Vergessen Sie nicht, die HTTP-Anforderung HEAD immer an den Proxy zu senden, um sicherzustellen, dass der Cache auf dem neuesten Stand ist.

13

Hier ist ein Beispiel für das Cachen mit EhCache. Dieser Code wird in mehreren Projekten zum Implementieren von Ad-hoc-Caching verwendet.

1) Platzieren Sie Ihren Cache im globalen Kontext. (Vergessen Sie nicht, den Listener in WEB.XML hinzuzufügen.)

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) Rufen Sie die Cache-Instanz bei Bedarf ab. d.h. von einem Servlet:

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

3) Fragen Sie den Cache ab, bevor Sie eine teure Operation ausführen.

        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) Vergessen Sie auch nicht, zwischengespeicherte Objekte gegebenenfalls zu ungültig zu machen.

Weitere Beispiele finden Sie hier

37
javito

Option # 1: Verwenden Sie eine Open Source-Caching-Bibliothek wie EHCache.

Implementieren Sie keinen eigenen Cache, wenn es eine Reihe guter Open-Source-Alternativen gibt, die Sie verwenden können. Die Implementierung eines eigenen Cache ist viel komplexer, als die meisten Leute erkennen. Wenn Sie nicht genau wissen, was Sie tun, um Threads auszuführen, können Sie leicht das Rad neu erfinden und einige sehr schwierige Probleme lösen.

Ich würde EHCache empfehlen, es steht unter einer Apache-Lizenz. Sie wollen die EHCace-Codebeispiele betrachten.

Option # 2: Verwenden Sie Squid

Eine noch einfachere Lösung für Ihr Problem wäre die Verwendung von Squid ... Setzen Sie Squid zwischen den Prozess, der das Abfragen der Daten fordert, und das System, das die Anforderung stellt: http://www.squid-cache.org/

8
Tim O'Brien

Nachdem ich mich etwas umgesehen habe, scheint es mir am einfachsten zu sein, das zu erreichen, was ich (innerhalb der in der Frage beschriebenen Anforderungen und akzeptablen Einschränkungen) benötigt, mein Caching-Objekt zum Servlet-Kontext hinzuzufügen und danach zu suchen (oder es herumlaufen, wo nötig.

Ich habe gerade mein Konfigurationsladeprogramm von einem ServletContextListener instanziiert und innerhalb der contextInitialized () - Methode mit ServletContext.setAttribute () in ServletContext gespeichert. Mit request.getSession (). GetServletContext (). GetAttribute () können Sie die Servlets dann problemlos nachschlagen.

Ich nehme an, dies ist der richtige Weg, um dies ohne die Einführung einer Feder oder eines anderen Abhängigkeitsinjektionsrahmens zu tun.

1
LordOfThePigs

Bref, Sie können diese fertige Federecache-Konfiguration verwenden

1- ehcache.xml : zeige die globale Konfiguration von 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 : erstellt EhCacheManagerFactoryBean und 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- Inject "myCache" -Bean in Ihrer Business-Klasse, siehe das folgende Beispiel, um mit dem Abrufen und Einfügen eines Objekts in den Cache zu beginnen.

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

} 
1

Ich hatte keine Probleme damit, eine zwischengespeicherte Objektinstanz in ServletContext zu platzieren. Vergessen Sie nicht die anderen 2 Optionen (Anforderungsbereich, Sitzungsbereich) mit den setAttributes-Methoden dieser Objekte. Alles, was in Webcontainern und J2ee-Servern nativ unterstützt wird, ist gut (mit gut meine ich ist vom Hersteller unabhängig und ohne schwere J2ee-Librarires wie Spring). Meine größte Anforderung ist, dass Server in 5-10 Sekunden einsatzbereit sind. 

Ich mag die Caching-Lösung wirklich nicht, weil es so einfach ist, sie auf lokalen Maschinen zum Laufen zu bringen, und schwer, sie auf Produktionsmaschinen zu installieren. EHCACHE, Infinispan etc .. Wenn Sie keine clusterweite Replikation/Verteilung benötigen, die eng in das Java-Ökosystem integriert ist, können Sie REDIS (NOSQL-Datenbank) oder nodejs verwenden. Insbesondere 

Caching kann sehr einfach sein, und hier ist die reine Java-Lösung (keine Frameworks):

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

Bitte beachten Sie diesen Link für weitere Verbesserungen.

0
Mitja Gustin