Usa Memcache

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

Cómo configurar Memcache

  1. Ve a la página de Memcache de Google Cloud Platform Console.
    Ir a la página de Memcache
  2. Selecciona el nivel de servicio de Memcache que quieres 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 se incluye en el paquete de 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 considerar el uso de la API de Memcache de bajo nivel para acceder a más características del servicio subyacente.

Obtén una instancia de caché

Usas una implementación de la interfaz de javax.cache.Cache para interactuar con la caché. Obtienes una instancia de la caché con CacheFactory, que consigues de un método estático en CacheManager. El código siguiente 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 de createCache() de CacheFactory toma un mapa de las 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: almacenas claves y valores con el método put() y recuperas 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 el método putAll() con un Mapa como su argumento.

Para quitar un valor de la caché (desalojarlo de inmediato), llama al método de 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 existe un valor con esa clave en la caché. El método isEmpty() comprueba si la caché está vacía. El método size() muestra la cantidad de valores que aparecen actualmente 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: valores que alcanzan su vencimiento en una cantidad de tiempo determinada relativa al momento en que se establecen, como segundos expresados con un número entero
  • GCacheFactory.EXPIRATION_DELTA_MILLIS: valores que alcanzan su vencimiento en una cantidad de tiempo determinada relativa al momento en que se establecen, como milisegundos expresados con un número entero
  • GCacheFactory.EXPIRATION: valores que alcanzan su vencimiento en una fecha y hora determinadas, 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 hay un valor con la clave y reemplaza un valor existente si hay un valor con la clave. Es la propiedad predeterminada.
  • MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT: agrega el valor si no hay un valor con la clave; no hagas nada si tienes la clave.
  • MemcacheService.SetPolicy.REPLACE_ONLY_IF_PRESENT: no hagas nada si no hay un valor con la clave; reemplaza un valor existente si hay 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 obtienes llamando el 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 de aciertos y errores. Las cantidades de aciertos y errores se mantienen de manera indefinida, pero se pueden restablecer debido a estados transitorios de los servidores de Memcache.

Cómo supervisar Memcache en Google Cloud Platform Console

  1. Ve a la página de Memcache de Google Cloud Platform 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 un porcentaje y la cantidad sin procesar de aciertos y errores de Memcache.
    • Elementos en 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.
    • Consulta el artículo Recomendaciones sobre Memcache en App Engine para obtener sugerencias sobre cómo distribuir las cargas de forma más uniforme en el espacio de claves.

Maneja escrituras simultáneas

Si vas a actualizar el valor de una clave de Memcache que puede 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 y permiten que varias solicitudes manejadas en simultáneo actualicen el valor de la misma clave de Memcache de forma atómica.

El fragmento de código siguiente 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 intentos a fin de evitar que el bloqueo se prolongue lo suficiente como para que se agote el tiempo de espera de tu solicitud de App Engine.

Pasos siguientes

¿Te ha resultado útil esta página? Enviar comentarios:

Enviar comentarios sobre...

Entorno estándar de App Engine para Java 8