Utilizzo di memcache

Questa pagina descrive come configurare e monitorare il servizio memcache per la tua applicazione utilizzando la console Google Cloud. Descrive inoltre come svolgere le attività comuni utilizzando l'interfaccia JCache e come gestire le scritture simultanee utilizzando l'API Memcache di App Engine di basso livello per Java. Per saperne di più su memcache, leggi la panoramica di Memcache.

Configurazione di memcache

  1. Vai alla pagina Memcache nella console Google Cloud.
    Vai alla pagina di memcache
  2. Seleziona il livello di servizio memcache che vuoi utilizzare:

    • Condiviso (impostazione predefinita): senza costi e fornisce la capacità della cache secondo il criterio del "best effort".
    • Dedicata: la fatturazione in base ai GB/ora di dimensioni della cache e fornisce una capacità della cache fissa assegnata esclusivamente alla tua applicazione.

    Scopri di più sulle classi di servizio disponibili in Panoramica di Memcache.

Utilizzo di JCache

L'SDK Java di App Engine supporta l'interfaccia JCache (JSR 107) per l'accesso a memcache. L'interfaccia è inclusa nel pacchetto javax.cache.

Con JCache, puoi impostare e ottenere valori, controllare la scadenza dei valori dalla cache, ispezionare i contenuti della cache e ottenere statistiche sulla cache. Puoi utilizzare gli "ascoltatori" anche per aggiungere un comportamento personalizzato quando imposti ed elimini i valori.

L'implementazione di App Engine tenta di implementare un sottoinsieme fedele dello standard API JCache. Per ulteriori informazioni su JCache, vedi JSR 107. Tuttavia, invece di utilizzare JCache, ti consigliamo di utilizzare l'API Memcache di basso livello per accedere a più funzionalità del servizio sottostante.

Recupero di un'istanza di cache

Utilizzi un'implementazione dell'interfaccia di javax.cache.Cache per interagire con la cache. Per ottenere un'istanza Cache, puoi utilizzare un valore Cache Producer, ottenuto da un metodo statico in CacheManager. Il seguente codice ottiene un'istanza Cache con la configurazione predefinita:

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) {
            // ...
        }

Il metodo createCache() di CacheFact utilizza una mappa delle proprietà di configurazione. Queste proprietà sono descritte di seguito. Per accettare i valori predefiniti, assegna al metodo una mappa vuota.

Mettere e ottenere valori

La cache si comporta come una mappa: puoi archiviare chiavi e valori utilizzando il metodo put() e recuperare valori con il metodo get(). Puoi utilizzare qualsiasi oggetto Serializable per la chiave o il valore.

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

Per inserire più valori, puoi chiamare il metodo putAll() con una mappa come argomento.

Per rimuovere un valore dalla cache (per rimuoverlo immediatamente), chiama il metodo remove() con la chiave come argomento. Per rimuovere ogni valore dalla cache per l'applicazione, chiama il metodo clear().

Il metodo containsKey() accetta una chiave e restituisce un valore boolean (true o false) per indicare se nella cache è presente un valore con quella chiave. Il metodo isEmpty() verifica se la cache è vuota. Il metodo size() restituisce il numero di valori attualmente presenti nella cache.

Configurazione della scadenza in corso...

Per impostazione predefinita, tutti i valori rimangono nella cache il più a lungo possibile fino a quando non vengono rimossi a causa dell'uso eccessivo della memoria, vengono rimossi esplicitamente dall'app o non sono più disponibili per un altro motivo (ad esempio un'interruzione). L'app può specificare una data di scadenza per i valori, ovvero un periodo di tempo massimo per cui il valore sarà disponibile. La scadenza può essere impostata come periodo di tempo rispetto al momento in cui viene impostato il valore o come data e ora assolute.

Puoi specificare il criterio di scadenza utilizzando le proprietà di configurazione quando crei l'istanza Cache. Tutti i valori inseriti con quell'istanza utilizzano lo stesso criterio di scadenza. Ad esempio, per configurare un'istanza Cache in modo che scadano i valori un'ora (3600 secondi) dopo l'impostazione:

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) {
            // ...
        }

Le seguenti proprietà controllano la scadenza del valore:

  • GCacheFactory.EXPIRATION_DELTA: scadono i valori per il periodo di tempo specificato rispetto al momento in cui vengono inseriti, sotto forma di numero intero di secondi.
  • GCacheFactory.EXPIRATION_DELTA_MILLIS: scadono i valori del periodo di tempo specificato rispetto al momento in cui vengono inseriti, sotto forma di numero intero di millisecondi
  • GCacheFactory.EXPIRATION: scadono i valori nella data e all'ora specificate, come java.util.Date

Configurazione del criterio impostato

Per impostazione predefinita, l'impostazione di un valore nella cache comporta l'aggiunta del valore se non è presente alcun valore con la chiave specificata e sostituisce un valore se esiste un valore con la chiave specificata. Puoi configurare la cache in modo che unicamente aggiunga (protegga i valori esistenti) o sostituisca solo i valori (non aggiungere).

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

Le seguenti proprietà controllano il criterio impostato:

  • MemcacheService.SetPolicy.SET_ALWAYS: aggiungi il valore se non esiste alcun valore con la chiave; sostituisci un valore esistente se esiste un valore con la chiave. Si tratta del valore predefinito
  • MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT: aggiungi il valore se non esiste alcun valore con la chiave; non fare nulla se la chiave esiste.
  • MemcacheService.SetPolicy.REPLACE_ONLY_IF_PRESENT: non fare nulla se non esiste alcun valore con la chiave; sostituisci un valore esistente se esiste un valore con la chiave

Recupero delle statistiche della cache

L'app può recuperare le statistiche sull'utilizzo della cache. Queste statistiche sono utili per monitorare e ottimizzare il comportamento della cache. Puoi accedere alle statistiche utilizzando un oggetto CacheStatistics, che ottieni chiamando il metodo getCacheStatistics() della cache.

Le statistiche disponibili includono il numero di hit dalla cache (ricevi per chiavi esistenti), il numero di fallimenti della cache (ricezioni per chiavi inesistenti) e il numero di valori nella cache.

import javax.cache.CacheStatistics;

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

L'implementazione di App Engine non supporta la reimpostazione dei conteggi di hit e miss, che vengono mantenuti a tempo indeterminato, ma potrebbero essere reimpostati a causa di condizioni temporanee dei server memcache.

Monitoraggio di memcache nella console Google Cloud

  1. Vai alla pagina Memcache nella console Google Cloud.
    Vai alla pagina di Memcache
  2. Esamina i seguenti report:
    • Livello di servizio Memcache: indica se la tua applicazione utilizza il livello di servizio Condiviso o Dedicato. Se sei un proprietario del progetto, puoi passare da un progetto all'altro. Scopri di più sui livelli di servizio.
    • Rapporto hit: mostra la percentuale di richieste di dati pubblicate dalla cache, nonché il numero non elaborato di richieste di dati pubblicate dalla cache.
    • Elementi nella cache.
    • Età dell'elemento meno recente: l'età dell'elemento meno recente memorizzato nella cache. Tieni presente che l'età di un elemento viene reimpostata ogni volta che viene utilizzato, in lettura o in scrittura.
    • Dimensioni totali della cache.
  3. Puoi intraprendere una qualsiasi delle seguenti azioni:

    • Nuova chiave: aggiungi una nuova chiave alla cache.
    • Trova una chiave: recupera una chiave esistente.
    • Svuota cache: rimuovi tutte le coppie chiave-valore dalla cache.
  4. (Solo memcache dedicata) Scorri l'elenco delle chiavi attive.

    • Le "chiavi a scorrimento" sono chiavi che ricevono più di 100 query al secondo (QPS) nella memcache.
    • Questo elenco include fino a 100 tasti di scelta rapida, ordinati in base al valore QPS più elevato.

Gestione delle scritture simultanee

Se aggiorni il valore di una chiave memcache che potrebbe ricevere altre richieste di scrittura simultanee, devi utilizzare i metodi memcache di basso livello putIfUntouched e getIdentifiable anziché put e get. I metodi putIfUntouched e getIdentifiable evitano le condizioni di gara consentendo a più richieste gestite contemporaneamente di aggiornare a livello atomico il valore della stessa chiave memcache.

Lo snippet di codice riportato di seguito mostra un modo per aggiornare in modo sicuro il valore di una chiave che potrebbe avere richieste di aggiornamento in parallelo da altri client:

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

Un perfezionamento che puoi aggiungere a questo codice campione prevede l'impostazione di un limite per il numero di nuovi tentativi, per evitare il blocco per un periodo così lungo che la richiesta di App Engine scade.

Passaggi successivi