Usa Memcache

En esta página, se describe cómo configurar y supervisar el servicio de Memcache para la aplicación con Google Cloud Console. También se explica cómo realizar tareas comunes con la interfaz de JCache y cómo controlar operaciones de escritura simultáneas mediante la API de Memcache de App Engine de bajo nivel para Java. Si quieres aprender más sobre Memcache, consulta Descripción general de Memcache.

Cómo configurar Memcache

  1. Ve a la página de Memcache en Google Cloud Console.
    Ir a la página de Memcache
  2. Selecciona el nivel de servicio de Memcache que desees usar:

    • Compartido (predeterminado): es gratuito y proporciona capacidades de caché sobre la base del "mejor esfuerzo".
    • Dedicado: se factura por GB por hora de tamaño de caché y proporciona una capacidad de caché fija asignada exclusivamente a tu aplicación.

    Puedes obtener más información sobre clases de servicio disponibles en Descripción general de Memcache.

Cómo usar JCache

El SDK de Java de App Engine admite la interfaz de JCache (JSR 107) para acceder a Memcache. La interfaz está incluida en el paquete javax.cache.

Con JCache, puedes configurar y obtener valores, controlar cómo vencen los valores de la caché, inspeccionar el contenido de la caché y obtener estadísticas sobre esta. También puedes usar "agentes de escucha" para agregar un comportamiento personalizado cuando configuras y borras valores.

La implementación de App Engine intenta implementar un subconjunto leal de la API de JCache estándar. (Para obtener más información sobre JCache, consulta JSR 107). Sin embargo, en lugar de usar JCache, te recomendamos usar la API de Memcache de bajo nivel para acceder a más funciones del servicio subyacente.

Obtén una instancia de caché

Usa una implementación de la interfaz de javax.cache.Cache para interactuar con la caché. Obtén una instancia de la caché con CacheFactory, que puedes conseguir a partir de un método estático en CacheManager. Con el siguiente código, se obtiene una instancia de caché con la configuración predeterminada:

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

El método createCache() de CacheFactory toma un mapa de propiedades de configuración. Estas propiedades se analizan a continuación. Si quieres aceptar los valores predeterminados, asigna un mapa vacío para el método.

Coloca y obtén valores

La caché se comporta como un mapa: Debes almacenar claves y valores con el método put() y recuperar valores con el método get(). Puedes usar cualquier objeto que se pueda serializar para la clave o el valor.

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

Para colocar varios valores, puedes llamar al método putAll() con un mapa como su argumento.

A fin de quitar un valor de la caché (para expulsarlo de inmediato), llama al método remove() con la clave como su argumento. Para quitar todos los valores de la caché de la aplicación, llama al método clear().

El método containsKey() toma una clave y muestra un boolean (true o false) para indicar si un valor con esa clave existe en la caché. El método isEmpty() comprueba si la caché está vacía. El método size() muestra la cantidad de valores que se encuentran en la caché.

Configura el vencimiento

De forma predeterminada, todos los valores permanecen en la caché el mayor tiempo posible, hasta que se desalojan debido a la presión de la memoria, se quitan de manera explícita con la aplicación o se inhabilitan por otro motivo (como una interrupción). La aplicación puede especificar un período de vencimiento para los valores, una cantidad máxima de tiempo de disponibilidad del valor. El tiempo de vencimiento se puede configurar como una cantidad de tiempo relativa al momento en que se configura el valor, o como una fecha y hora absolutas.

Especificas la política de vencimiento con las propiedades de configuración cuando creas la instancia de la caché. Todos los valores colocados con esa instancia utilizan la misma política de vencimiento. Por ejemplo, para configurar una instancia de caché y que los valores alcancen su vencimiento una hora (3,600 segundos) después de su configuración:

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

Las siguientes propiedades controlan el vencimiento del valor:

  • GCacheFactory.EXPIRATION_DELTA: Los valores vencen en una cantidad de tiempo determinada en relación con el momento en que se colocan, como un número entero de segundos.
  • GCacheFactory.EXPIRATION_DELTA_MILLIS: Los valores vencen en una cantidad de tiempo determinada relativa al momento en que se colocan, como un número entero de milisegundos.
  • GCacheFactory.EXPIRATION: Los valores vencen en la fecha y hora especificadas, como java.util.Date.

Configura la política establecida

De manera predeterminada, establecer un valor en la caché permite agregarlo si no hay un valor con la clave dada y reemplazarlo si hay un valor con la clave dada. Puedes configurar la caché para agregar solamente (proteger valores existentes) o reemplazar solamente los valores (no agregar).

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

Las siguientes propiedades controlan la política establecida:

  • MemcacheService.SetPolicy.SET_ALWAYS: Agrega el valor si no existe un valor con la clave y reemplaza un valor existente si existe un valor con la clave; este es el valor predeterminado.
  • MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT: Agrega el valor si no existe ningún valor con la clave, no realiza ninguna acción si la clave existe.
  • MemcacheService.SetPolicy.REPLACE_ONLY_IF_PRESENT: No realiza ninguna acción si no existe ningún valor con la clave; reemplaza un valor existente si existe un valor con la clave.

Recupera estadísticas de caché

La aplicación puede recuperar estadísticas sobre su propio uso de la caché. Estas estadísticas son útiles para supervisar y ajustar el comportamiento de la caché. Puedes acceder a las estadísticas con un objeto CacheStatistics, que se obtiene mediante una llamada al método getCacheStatistics() de la caché.

Las estadísticas disponibles incluyen la cantidad de aciertos de caché (para claves existentes), la cantidad de errores de caché (para claves no existentes) y la cantidad de valores en la caché.

import javax.cache.CacheStatistics;

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

La implementación de App Engine no admite el restablecimiento de las cantidades hit y miss, que se mantienen de manera indefinida, pero que se pueden restablecer debido a condiciones transitorias de los servidores de Memcache.

Supervisa Memcache en Google Cloud Console

  1. Ve a la página de Memcache en Google Cloud Console.
    Ir a la página de Memcache
  2. Consulta los siguientes informes:
    • Nivel de servicio de Memcache: Muestra si tu aplicación usa el nivel de servicio Compartido o Dedicado. Si eres propietario del proyecto, puedes alternar entre ambos. Obtén más información sobre los niveles de servicio.
    • Proporción de aciertos: Muestra el porcentaje de solicitudes de datos que se entregaron desde la caché y la cantidad de solicitudes de datos sin procesar que se entregaron desde la caché.
    • Elementos en la caché.
    • Edad del elemento más antiguo: Edad del elemento almacenado en caché más antiguo. Ten en cuenta que la edad de un elemento se restablece cada vez que se usa, ya sea en operaciones de lectura o de escritura.
    • Tamaño total de caché
  3. Puedes realizar cualquiera de las siguientes acciones:

    • Clave nueva: agrégale una clave a la caché.
    • Encontrar una clave: recupera una clave existente.
    • Limpiar caché: quita todos los pares clave-valor de la caché.
  4. (Solo para Memcache dedicada) Consulta la lista de claves populares.

    • Las "claves populares" son claves que reciben más de 100 consultas por segundo (QPS) en Memcache.
    • La lista incluye hasta 100 claves populares ordenadas de mayor a menor cantidad de QPS.

Cómo administrar escrituras simultáneas

Si deseas actualizar el valor de una clave de Memcache que podría recibir otras solicitudes de escritura simultáneas, debes usar los métodos de Memcache de bajo nivel putIfUntouched y getIdentifiable en lugar de put y get. Los métodos putIfUntouched y getIdentifiable evitan las condiciones de carrera, ya que permiten que varias solicitudes que se administran al mismo tiempo actualicen el valor de la misma clave de Memcache de forma atómica.

En el siguiente fragmento de código, se muestra una forma de actualizar de manera segura el valor de una clave que podría tener solicitudes de actualización simultáneas de otros clientes:

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

Una mejora que podrías agregar a este código de muestra es establecer un límite en la cantidad de reintentos a fin de evitar que el bloqueo se prolongue lo suficiente como para que se agote el tiempo de espera de la solicitud a App Engine.

Pasos siguientes