web-dev-qa-db-fra.com

Application Web Java: Comment mettre en œuvre des techniques de mise en cache?

Je développe une application Web Java qui fonde son comportement sur de gros fichiers de configuration XML chargés à partir d'un service Web. Étant donné que ces fichiers ne sont pas réellement nécessaires jusqu'à l'accès à une section particulière de l'application, ils sont chargés paresseusement. Lorsqu'un de ces fichiers est requis, une requête est envoyée au service Web pour récupérer le fichier correspondant. Certains fichiers de configuration étant susceptibles d’être utilisés beaucoup, beaucoup plus souvent que d’autres, je souhaiterais configurer une sorte de mise en cache (avec peut-être une heure d’expiration) pour éviter de demander le même fichier à plusieurs reprises.

Les fichiers renvoyés par le service Web sont les mêmes pour tous les utilisateurs dans toutes les sessions. Je n'utilise pas JSP, JSF ou tout autre framework de fantaisie, juste des servlets simples.

Ma question est la suivante: quelle est la pratique recommandée pour implémenter un tel cache statique global dans une application Web Java? Une classe singleton est-elle appropriée ou y aura-t-il des comportements étranges dus aux conteneurs J2EE? Devrais-je exposer quelque chose quelque part via JNDI? Que dois-je faire pour que mon cache ne soit pas vissé dans les environnements en cluster (c'est correct, mais pas nécessaire, d'avoir un cache par serveur en cluster)? 

Compte tenu des informations ci-dessus, serait-il une implémentation correcte de placer un objet responsable de la mise en cache en tant qu'attribut ServletContext?

Remarque: je ne veux pas tous les charger au démarrage et en finir avec cela car cela 

1). surcharger le service Web à chaque démarrage de mon application
2). Les fichiers peuvent changer pendant que mon application est en cours d'exécution, il me faudrait donc les interroger quand même.
3). J'aurais toujours besoin d'un cache accessible globalement, alors ma question reste d'actualité

Mise à jour: L'utilisation d'un proxy de mise en cache (tel que squid) peut être une bonne idée, mais chaque requête adressée au service Web enverra une requête XML assez volumineuse dans la publication. Les données, qui peuvent être différentes à chaque fois. Seule l’application Web sait réellement que deux appels différents au service Web sont en réalité équivalents.

Merci de votre aide

23
LordOfThePigs

Votre question contient plusieurs questions distinctes ensemble. Commençons lentement. ServletContext est un bon endroit où vous pouvez stocker des poignées dans votre cache. Mais vous payez en ayant un cache par instance de serveur. Cela ne devrait pas poser de problème. Si vous souhaitez enregistrer le cache dans une plage plus large, envisagez de l'enregistrer dans JNDI.

Le problème avec la mise en cache. En gros, vous récupérez xml via webservice. Si vous accédez à ce service Web via HTTP, vous pouvez installer un serveur proxy HTTP simple qui gère la mise en cache de XML. L'étape suivante consistera à mettre en cache le fichier XML résolu dans une sorte de cache d'objets local. Ce cache peut exister par serveur sans aucun problème. Dans ce second cas, EHCache fera un travail parfait. Dans ce cas, la chaîne de traitement sera comme ceci Client - http request -> servlet -> look into local cache - if not cached -> look into http proxy (xml files) -> do proxy job (http to webservice)

Avantages:

  • Cache local par instance de serveur, qui ne contient que des objets des xml demandés
  • Un proxy http fonctionnant sur le même matériel que notre application Web.
  • Possibilité de redimensionner la webapp sans ajouter de nouveaux proxies http pour les fichiers xml.

Les inconvénients:

  • Prochain niveau d'infrastructure
  • +1 point d'échec (proxy http)
  • Déploiement plus compliqué

Mise à jour: n'oubliez pas de toujours envoyer la demande HTTP HEAD dans un proxy pour vous assurer que le cache est à jour.

13
Rastislav Komara

Option n ° 1: Utiliser une bibliothèque de mise en cache open source telle que EHCache

N'implémentez pas votre propre cache lorsqu'il existe un certain nombre de bonnes alternatives open source que vous pouvez utiliser et commencer à utiliser. L'implémentation de votre propre cache est beaucoup plus complexe que la plupart des gens ne le pensent et si vous ne savez pas exactement ce que vous faites avec le filetage, vous pourrez facilement réinventer la roue et résoudre des problèmes très difficiles.

Je recommanderais EHCache, il est sous licence Apache. Vous voudrez jeter un oeil sur les exemples de code EHCace .

Option n ° 2: Utiliser du calmar

Une solution encore plus simple à votre problème serait d’utiliser Squid ... Insérez Squid entre le processus qui demande la mise en cache des données et celui qui en fait la demande: http://www.squid-cache.org/

8
Tim O'Brien

Après avoir cherché un peu plus autour de moi, il semble que le moyen le plus simple d’obtenir ce dont j'ai besoin (dans les conditions requises et dans les limites acceptables décrites dans la question) consiste à ajouter mon objet de mise en cache au contexte du servlet et à le rechercher (ou en le faisant circuler) si nécessaire.

Je viens d'instancier mon chargeur de configuration à partir d'un ServletContextListener et, dans la méthode contextInitialized (), je l'enregistre dans le ServletContext à l'aide de ServletContext.setAttribute (). Il est alors facile de le rechercher à partir des servlets eux-mêmes à l'aide de request.getSession (). GetServletContext (). GetAttribute ().

Je suppose que c'est la bonne façon de le faire sans introduire le printemps ou tout autre framework d'injection de dépendance.

1
LordOfThePigs

Bref, vous pouvez utiliser cette configuration prête ehcache de printemps

1- ehcache.xml : affiche la configuration globale d'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 : créez EhCacheManagerFactoryBean et 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- Injectez le bean "myCache" dans votre classe affaires, voir l'exemple suivant pour commencer à obtenir et à mettre un objet dans votre cache.

@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

Je n'ai rencontré aucun problème pour mettre une instance d'objet mis en cache dans ServletContext. N'oubliez pas les 2 autres options (étendue de la requête, étendue de la session) avec les méthodes setAttributes de cet objet. Tout ce qui est supporté nativement dans les webcontainers et les serveurs j2ee est bon (par bon je veux dire, il est indépendant du vendeur, et sans les bibliothèques j2ee lourdes comme Spring). Ma principale exigence est que les serveurs soient opérationnels en 5 à 10 secondes. 

Je n'aime vraiment pas les solutions de mise en cache, car il est si facile de le faire fonctionner sur une machine locale et difficile de le faire sur des machines de production. EHCACHE, Infinispan, etc. Sauf si vous avez besoin d'une réplication/distribution à l'échelle du cluster, étroitement intégrée à l'écosystème Java, vous pouvez utiliser REDIS (base de données NOSQL) ou nodejs ... N'importe quoi avec une interface HTTP fera l'affaire. Notamment 

La mise en cache peut être très simple, et voici la solution Java pure (sans framework):

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

Veuillez vous référer à ce link pour d’autres améliorations.

0
Mitja Gustin