Utilizzo di memcache

In questa pagina viene descritto come configurare e monitorare il servizio memcache per utilizzando la console Google Cloud. Descrive inoltre come eseguire attività comuni utilizzando JCache e come gestire le scritture simultanee utilizzando l'ambiente App Engine API memcache per Java. Per saperne di più su memcache, consulta la panoramica di Memcache.

Configurazione di memcache

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

    • Condivisa (impostazione predefinita): gratuita e fornisce capacità della cache su una base di "best effort".
    • Dedicata: fattura in base ai GB/ora di dimensione della cache e fornisce una tariffa di cache 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 accedere a memcache. L'interfaccia è inclusa nel pacchetto javax.cache.

Con JCache, puoi impostare e ottenere valori, controllare le modalità di scadenza dei valori dalla cache, ispezionare i contenuti della cache e ottenere statistiche sulla cache. Puoi anche utilizzare "ascoltatori" per aggiungere un comportamento personalizzato durante l'impostazione e l'eliminazione dei valori.

L'implementazione di App Engine cerca di implementare un sottoinsieme fedele dello standard API JCache. Per ulteriori informazioni su JCache, consulta la JSR 107. Tuttavia, anziché utilizzare JCache, ti consigliamo di utilizzare l'API Memcache di basso livello per accedere ad altre funzionalità del servizio sottostante.

Recupero di un'istanza cache

Utilizzi un'implementazione dell'interfaccia javax.cache.Cache per interagire con la cache. Per ottenere un'istanza Cache si utilizza un Cache dalle risorse predefinite, ottenuto da un metodo statico su 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 CacheFactory accetta una mappa di 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: archivi le chiavi e i valori utilizzando il metodo put() e recuperi i valori utilizzando 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 eseguirne l'espulsione immediata), 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() prende 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 nella cache.

Configurazione della scadenza

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

Specifica il criterio di scadenza utilizzando le proprietà di configurazione quando crei l'istanza Cache. Tutti i valori inseriti con l'istanza utilizzano lo stesso criterio di scadenza. Ad esempio, per configurare un'istanza Cache in modo che scada 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: fai scadere i valori per un determinato periodo di tempo rispetto al momento in cui vengono inseriti, sotto forma di numero intero di secondi
  • GCacheFactory.EXPIRATION_DELTA_MILLIS: imposta la scadenza dei valori dopo un determinato periodo di tempo rispetto al momento in cui vengono inseriti, come numero intero di millisecondi
  • GCacheFactory.EXPIRATION: fai scadere i valori alla data e all'ora specificate, come java.util.Date

Configurazione del criterio impostato

Per impostazione predefinita, l'impostazione di un valore nella cache aggiunge il valore se non è presente alcun valore per la chiave specificata e ne sostituisce uno se è presente un valore per la chiave specificata. Puoi configurare la cache in modo che aggiunga solo (proteggi i valori esistenti) o sostituisca solo i valori (non li aggiunge).

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. Sostituiscine uno esistente se esiste un valore con la chiave. questo è il valore predefinito
  • MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT: aggiungi il valore se non esiste un valore con la chiave, non fare nulla se la chiave esiste
  • MemcacheService.SetPolicy.REPLACE_ONLY_IF_PRESENT: non fare nulla se non esiste un valore con la chiave, sostituisci un valore esistente se esiste un valore con la chiave

Recupero delle statistiche della cache in corso

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

Le statistiche disponibili includono il numero di successi della cache (ricevi per chiavi esistenti), il numero di fallimenti della cache (ricevi per chiavi che non esistono) 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 il ripristino dei conteggi hit e miss, che vengono mantenuti a tempo indeterminato, ma possono essere reimpostati a causa di condizioni transitorie dei server memcache.

Monitoraggio di memcache nella console Google Cloud

  1. Vai alla pagina Memcache nella console Google Cloud.
    Vai alla pagina Memcache
  2. Esamina i seguenti report:
    • Livello di servizio Memcache: mostra se la tua applicazione utilizza l'opzione o un livello di servizio dedicato. Se sei un proprietario del progetto, puoi passare da una modalità all'altra. Impara Scopri di più sui livelli di servizio.
    • Percentuale di hit: mostra la percentuale di richieste di dati pubblicate da dalla cache, oltre al numero non elaborato di richieste di dati gestite 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, letto o scritto.
    • Dimensioni totali cache.
  3. Puoi eseguire una delle seguenti azioni:

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

    • "Tasti rapidi" si tratta di chiavi che ricevono più di 100 query al secondo (QPS) in memcache.
    • Questo elenco include fino a 100 tasti di scelta rapida, ordinati in base alla QPS più alta.

Gestione delle scritture simultanee

Se stai aggiornando 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 della stessa chiave memcache a livello atomico.

Lo snippet di codice riportato di seguito mostra un modo per aggiornare in modo sicuro il valore di una chiave potrebbero ricevere richieste di aggiornamento simultanee 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 di esempio è impostare un limite al numero di tentativi, per evitare un blocco così lungo da far scadere il tempo di attesa della richiesta di App Engine.

Passaggi successivi