Utiliser Memcache

Cette page explique comment configurer et surveiller le service Memcache pour votre application à l'aide de la console Google Cloud Platform. Elle explique également comment effectuer des tâches courantes à l'aide de l'interface JCache et comment gérer des écritures simultanées à l'aide de l'API App Engine memcache de bas niveau pour Java. Pour en savoir plus sur Memcache, consultez la page Présentation de Memcache.

Configurer Memcache

  1. Accédez à la page "Memcache" dans la console Google Cloud Platform.
    Accéder à la page "Memcache"
  2. Sélectionnez le niveau de service Memcache que vous souhaitez utiliser.

    • Partagé : (valeur par défaut) niveau de service gratuit qui attribue la capacité de cache de la manière la plus optimale possible.
    • Dédié : niveau de service facturé à l'heure et par Go de cache utilisé. Il fournit une capacité de cache fixe qui est attribuée exclusivement à votre application.

    Pour en savoir plus sur les classes de service disponibles, consultez la page Présentation de Memcache.

Utilisation de JCache

Le SDK Java d'App Engine est compatible avec l'interface d'accès à memcache JCache (JSR 107). L'interface est incluse dans le package javax.cache.

Avec JCache, vous pouvez définir et extraire des valeurs, contrôler l'expiration des valeurs et leur suppression de la mémoire cache, inspecter le contenu du cache et obtenir des statistiques concernant ce dernier. Vous pouvez également utiliser des "écouteurs" pour personnaliser le comportement à la définition et à la suppression de valeurs.

La mise en œuvre d'App Engine tente d'implémenter un sous-ensemble fidèle du standard d'API JCache. (Pour plus d'informations sur JCache, consultez la norme JSR 107.) Cependant, au lieu d'utiliser JCache, vous pouvez envisager d'utiliser l'API Memcache de bas niveau afin d'avoir accès à davantage de fonctionnalités du service sous-jacent.

Obtenir une instance de cache

Vous utilisez une mise en œuvre de l'interface javax.cache.Cache pour interagir avec le cache. Vous obtenez une instance de cache à l'aide d'un objet CacheFactory lui-même issu d'une méthode statique du CacheManager. Le code suivant permet d'obtenir une instance de cache avec la configuration par défaut :

import java.util.Collections;
import javax.cache.Cache;
import javax.cache.CacheException;
import javax.cache.CacheFactory;
import javax.cache.CacheManager;

// ...
        Cache cache;
        try {
            CacheFactory cacheFactory = CacheManager.getInstance().getCacheFactory();
            cache = cacheFactory.createCache(Collections.emptyMap());
        } catch (CacheException e) {
            // ...
        }

La méthode createCache() de la fabrique CacheFactory utilise un élément Map représentant les propriétés de configuration. Ces propriétés sont présentées ci-dessous. Pour accepter les valeurs par défaut, attribuez à la méthode un élément Map vide.

Insérer et récupérer des valeurs

L'objet Cache se comporte comme un élément Map : vous y stockez des clés et des valeurs à l'aide de la méthode put(), puis vous pouvez récupérer les valeurs à l'aide de la méthode get(). Vous pouvez utiliser n'importe quel objet sérialisable pour la clé ou la valeur.

        String key;      // ...
        byte[] value;    // ...

        // Put the value into the cache.
        cache.put(key, value);

        // Get the value from the cache.
        value = (byte[]) cache.get(key);

Pour y stocker plusieurs valeurs, vous pouvez appeler la méthode putAll() sur un argument Map.

Pour supprimer une valeur du cache (pour l'expulser immédiatement), appelez la méthode remove() avec comme argument la clé. Pour supprimer toutes les valeurs du cache de l'application, appelez la méthode clear().

La méthode containsKey() prend une clé et renvoie un boolean (true ou false) pour indiquer s'il existe dans le cache une valeur associée à cette clé. La méthode isEmpty() teste si le cache est vide. La méthode size() renvoie le nombre de valeurs figurant actuellement dans le cache.

Configurer l'expiration

Par défaut, toutes les valeurs restent dans le cache le plus longtemps possible, c'est-à-dire jusqu'à ce qu'elles soient éliminées suite à la saturation de la mémoire, supprimées explicitement par l'application ou rendues indisponibles pour une autre raison (telle qu'une panne). L'application peut spécifier un délai d'expiration pour les valeurs, c'est-à-dire une durée maximale pendant laquelle la valeur sera disponible. Le délai d'expiration peut être paramétré comme une durée relative qui dépend du moment où la valeur a été définie ou comme un horodatage absolu.

Vous pouvez spécifier la règle d'expiration à l'aide des propriétés de configuration lors de la création de l'instance de cache. Toutes les valeurs insérées avec cette instance utilisent la même règle d'expiration. L'exemple suivant permet de configurer une instance de cache afin que les valeurs expirent une heure (3 600 secondes) après qu'elles ont été définies :

import java.util.HashMap;
import java.util.Map;
import javax.cache.Cache;
import javax.cache.CacheException;
import javax.cache.CacheFactory;
import javax.cache.CacheManager;
import javax.concurrent.TimeUnit;
import com.google.appengine.api.memcache.jsr107cache.GCacheFactory;

// ...
        Cache cache;
        try {
            CacheFactory cacheFactory = CacheManager.getInstance().getCacheFactory();
            Map<Object, Object> properties = new HashMap<>();
            properties.put(GCacheFactory.EXPIRATION_DELTA, TimeUnit.HOURS.toSeconds(1));
            cache = cacheFactory.createCache(properties);
        } catch (CacheException e) {
            // ...
        }

Les propriétés suivantes contrôlent l'expiration des valeurs :

  • GCacheFactory.EXPIRATION_DELTA : fait expirer les valeurs au bout de la durée renseignée, écoulée à partir du moment où elles sont stockées par Put, exprimée en un nombre entier (Integer) de secondes.
  • GCacheFactory.EXPIRATION_DELTA_MILLIS : fait expirer les valeurs au bout de la durée renseignée, écoulée à partir du moment où elles sont stockées par Put, exprimée en un nombre entier (Integer) de millisecondes
  • GCacheFactory.EXPIRATION : fait expirer les valeurs à la date et à l'heure indiquées, exprimée sous la forme d'un objet java.util.Date.

Configurer la règle de définition

Par défaut, la définition d'une valeur dans le cache a pour effet de l'ajouter si aucune valeur n'est associée à la clé spécifiée et de remplacer la valeur existante si une valeur correspond déjà à la clé spécifiée. Vous pouvez configurer le cache afin que les valeurs soient uniquement ajoutées (protection des valeurs existantes) ou remplacées (pas d'ajout).

import java.util.HashMap;
import java.util.Map;
import com.google.appengine.api.memcache.MemcacheService;

// ...
        Map<Object, Object> properties = new HashMap<>();
        properties.put(MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT, true);

Les propriétés suivantes contrôlent la règle de définition :

  • MemcacheService.SetPolicy.SET_ALWAYS : ajoute la valeur s'il n'existe aucune valeur associée à la clé, remplace la valeur existante s'il existe une valeur associée à la clé. C'est le comportement par défaut.
  • MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT : ajoute la valeur s'il n'existe aucune valeur associée à la clé, ne fait rien si la clé existe.
  • MemcacheService.SetPolicy.REPLACE_ONLY_IF_PRESENT : ne fait rien s'il n'existe aucune valeur associée à la clé, remplace la valeur existante s'il existe une valeur associée à la clé.

Récupérer les statistiques relatives au cache

L'application peut récupérer les statistiques relatives à sa propre utilisation du cache. Ces statistiques sont utiles pour contrôler et ajuster le comportement du cache. Vous accédez aux statistiques à l'aide d'un objet CacheStatistics, que vous obtenez en appelant la méthode getCacheStatistics() du cache.

Les statistiques disponibles incluent le nombre d'opérations réussies en mémoire cache (opérations Get exécutées pour les clés qui existent), le nombre d'échecs d'accès au cache (opérations Get exécutées pour les clés qui n'existent pas) et le nombre de valeurs présentes dans le cache.

import javax.cache.CacheStatistics;

        CacheStatistics stats = cache.getCacheStatistics();
        int hits = stats.getCacheHits();
        int misses = stats.getCacheMisses();

L'implémentation d'App Engine ne permet pas de redéfinir les taux de réussite et d'échec des recherches en mémoire cache. Les taux de réussite et d'échec sont maintenus indéfiniment, mais peuvent être redéfinis suite à des conditions transitoires affectant les serveurs de cache mémoire.

Surveiller Memcache dans la console Google Cloud Platform

  1. Accédez à la page "Memcache" dans la console Google Cloud Platform.
    Accéder à la page "Memcache"
  2. Consultez les rapports ci-dessous :
    • Niveau de service Memcache : indique si l'application utilise le niveau de service partagé ou dédié. Si vous êtes propriétaire du projet, vous pouvez basculer de l'un à l'autre. En savoir plus sur les niveaux de service.
    • Taux de succès : affiche le pourcentage ainsi que le nombre brut de succès et de défauts de cache survenus dans Memcache.
    • Éléments en cache.
    • Âge du plus ancien élément : âge de l'élément en cache le plus ancien. Notez que l'âge d'un élément est réinitialisé chaque fois que ce dernier est utilisé, qu'il soit lu ou écrit.
    • Taille totale du cache.
  3. Vous pouvez effectuer l'une des actions suivantes :

    • Nouvelle clé : ajoute une clé au cache.
    • Trouver une clé : récupère une clé existante.
    • Vider la mémoire cache : supprime toutes les paires clé/valeur du cache.
  4. (Service Memcache dédié uniquement) Parcourez la liste des Clés fréquemment utilisées.

    • Les clés de cette liste reçoivent plus de 100 requêtes par seconde (RPS) dans Memcache.
    • Cette liste peut comporter jusqu'à 100 clés, triées par ordre décroissant en fonction du nombre de RPS reçues.
    • Consultez la page Bonnes pratiques relatives au service Memcache d'App Engine pour savoir comment répartir la charge de manière plus uniforme au sein de l'espace de clés.

Traiter les écritures simultanées

Si vous mettez à jour la valeur d'une clé memcache susceptible de faire l'objet d'autres requêtes simultanées en écriture, vous devez utiliser les méthodes memcache de bas niveau putIfUntouched et getIdentifiable au lieu de put et get. Les méthodes putIfUntouched et getIdentifiable évitent les conditions de concurrence en autorisant le traitement simultané, de manière atomique, de plusieurs requêtes de mise à jour sur la même clé memcache.

L'extrait de code ci-dessous présente une manière de mettre à jour en toute sécurité la valeur d'une clé susceptible de faire l'objet de requêtes de mise à jour simultanées en provenance d'autres clients :

@SuppressWarnings("serial")
public class MemcacheConcurrentServlet extends HttpServlet {

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException,
      ServletException {
    String path = req.getRequestURI();
    if (path.startsWith("/favicon.ico")) {
      return; // ignore the request for favicon.ico
    }

    String key = "count-concurrent";
    // Using the synchronous cache.
    MemcacheService syncCache = MemcacheServiceFactory.getMemcacheService();

    // Write this value to cache using getIdentifiable and putIfUntouched.
    for (long delayMs = 1; delayMs < 1000; delayMs *= 2) {
      IdentifiableValue oldValue = syncCache.getIdentifiable(key);
      byte[] newValue = oldValue == null
          ? BigInteger.valueOf(0).toByteArray()
              : increment((byte[]) oldValue.getValue()); // newValue depends on old value
      resp.setContentType("text/plain");
      resp.getWriter().print("Value is " + new BigInteger(newValue).intValue() + "\n");
      if (oldValue == null) {
        // Key doesn't exist. We can safely put it in cache.
        syncCache.put(key, newValue);
        break;
      } else if (syncCache.putIfUntouched(key, oldValue, newValue)) {
        // newValue has been successfully put into cache.
        break;
      } else {
        // Some other client changed the value since oldValue was retrieved.
        // Wait a while before trying again, waiting longer on successive loops.
        try {
          Thread.sleep(delayMs);
        } catch (InterruptedException e) {
          throw new ServletException("Error when sleeping", e);
        }
      }
    }
  }

  /**
   * Increments an integer stored as a byte array by one.
   * @param oldValue a byte array with the old value
   * @return         a byte array as the old value increased by one
   */
  private byte[] increment(byte[] oldValue) {
    long val = new BigInteger(oldValue).intValue();
    val++;
    return BigInteger.valueOf(val).toByteArray();
  }
}

Vous pouvez affiner cet exemple de code en limitant le nombre de tentatives, afin d'éviter un blocage si long qu'il conduirait votre requête App Engine à expirer.

Étapes suivantes

Cette page vous a-t-elle été utile ? Évaluez-la :

Envoyer des commentaires concernant…

Environnement standard App Engine pour Java