Memcache verwenden

Auf dieser Seite wird beschrieben, wie Sie den Memcache-Dienst für Ihre Anwendung mit der Google Cloud Console konfigurieren und überwachen. Außerdem erfahren Sie, wie Sie allgemeine Aufgaben über die JCache-Schnittstelle ausführen und gleichzeitige Schreibvorgänge mit der untergeordneten App Engine Memcache API für Java verwalten. Weitere Informationen zu Memcache finden Sie in der Memcache-Übersicht.

Memcache konfigurieren

  1. Rufen Sie die Memcache-Seite in der Google Cloud Console auf.
    Zur Seite „Memcache“
  2. Wählen Sie das gewünschte Memcache-Service-Level aus:

    • Freigegeben (Standard): Kostenlos, bietet Cache-Kapazität auf Best-Effort-Basis.
    • Dediziert: Kostenpflichtig, die Abrechnung erfolgt nach Cache-Größe in GB/Stunden. Eine feste Cache-Kapazität steht exklusiv für Ihre Anwendung zur Verfügung.

    Weitere Informationen zu verfügbaren Dienstklassen finden Sie in der Übersicht über Memcache.

JCache verwenden

Das App Engine Java SDK unterstützt die JCache-Schnittstelle (JSR 107) für den Zugriff auf Memcache. Die Schnittstelle ist im Paket javax.cache enthalten.

Mit JCache können Sie Werte festlegen und abrufen, steuern, wie Werte ablaufen, die Inhalte des Caches inspizieren und Statistiken über den Cache abrufen. Sie können auch "Listener" verwenden, um Werte nach einem benutzerdefinierten Verhalten festzulegen und zu löschen.

Bei der App Engine-Implementierung wird versucht, eine normgerechte Teilmenge des JCache API-Standards zu implementieren. Weitere Informationen zu JCache finden Sie unter JSR 107. Als Alternative zu JCache empfiehlt sich die untergeordnete Memcache API. Sie können dadurch auf mehr Funktionen des zugrundeliegenden Dienstes zugreifen.

Cacheinstanz abrufen

Sie können über eine Implementierung der javax.cache.Cache-Schnittstelle mit dem Cache interagieren. Sie erhalten eine Cache-Instanz mithilfe eines CacheFactory, das Sie aus einer statischen Methode auf dem CacheManager abrufen. Mithilfe des folgenden Codes rufen Sie eine Cacheinstanz mit der Standardkonfiguration ab:

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

Die createCache()-Methode des CacheFactory zieht eine Map der Konfigurationseigenschaften heran. Diese Eigenschaften werden nachfolgend erörtert. Um die Standardwerte zu akzeptieren, stellen Sie eine leere Map für die Methode zur Verfügung.

Werte angeben und abrufen

Der Cache verhält sich wie eine Map: Sie speichern Schlüssel und Werte mithilfe der put()-Methode und erhalten Werte mithilfe der get()-Methode. Sie können sowohl für den Schlüssel als auch für den Wert ein beliebiges serialisierbares Objekt verwenden.

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

Wenn Sie mehrere Werte angeben möchten, können Sie eine putAll()-Methode mit einer Map als zugehöriges Argument aufrufen.

Wenn Sie einen Wert aus dem Cache entfernen möchten (der sofort gelöscht werden soll), rufen Sie die remove()-Methode mit dem Schlüssel als zugehöriges Argument auf. Um sämtliche Werte für die Anwendung aus dem Cache zu entfernen, rufen Sie die clear()-Methode auf.

Die containsKey()-Methode nimmt einen Schlüssel und gibt ein boolean(true oder false) zurück, um anzugeben, ob ein Wert mit dem entsprechenden Schlüssel im Cache vorhanden ist oder nicht. Die isEmpty()-Methode testet, ob der Cache leer ist. Die size()-Methode gibt die Anzahl der derzeit im Cache vorhandenen Werte zurück.

Ablaufzeit konfigurieren

Standardmäßig verbleiben alle Werte so lange wie möglich im Cache, bis sie aufgrund von Speicherauslastung gelöscht werden, ausdrücklich von der Anwendung entfernt werden oder aus anderen Gründen nicht mehr verfügbar sind (z. B. durch einen Systemausfall). Durch die Anwendung kann eine Ablaufzeit festgelegt werden, d. h. ein maximaler Zeitraum, für den der Wert verfügbar sein kann. Die Ablaufzeit kann als relativer Zeitraum in Bezug auf den Zeitpunkt der Festlegung des Werts oder absolut in Form eines Datums und einer Uhrzeit festgelegt werden.

Sie können die Ablaufrichtlinie mithilfe der Konfigurationseigenschaften festlegen, wenn Sie die Cache-Instanz erstellen. Sämtliche Werte, die für diese Instanz angegeben werden, verwenden dieselbe Ablaufrichtlinie. So konfigurieren Sie zum Beispiel, dass die Werte einer Cache-Instanz eine Stunde (3.600 Sekunden) nach ihrer Festlegung ablaufen:

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

Die folgenden Eigenschaften steuern den Ablauf der Werte:

  • GCacheFactory.EXPIRATION_DELTA: Die Werte laufen nach einem bestimmten Zeitraum (angegeben durch eine Ganzzahl an Sekunden) in Bezug auf ihre Festlegung ab.
  • GCacheFactory.EXPIRATION_DELTA_MILLIS: Die Werte laufen nach einem bestimmten Zeitraum (angegeben durch eine Ganzzahl an Millisekunden) in Bezug auf ihre Festlegung ab.
  • GCacheFactory.EXPIRATION: Die Werte laufen zu einem bestimmten Zeitpunkt ab (Datum und Uhrzeit als java.util.Date-Wert).

Festlegungsrichtlinie konfigurieren

Standardmäßig wird durch das Festlegen eines Werts im Cache der entsprechende Wert hinzugefügt, sofern noch kein Wert mit dem entsprechenden Schlüssel vorhanden ist. Falls bereits ein Wert mit dem entsprechenden Schlüssel vorhanden ist, wird dieser ersetzt. Sie können den Cache so konfigurieren, dass Werte entweder nur hinzugefügt (und somit vorhandene Werte geschützt) oder nur ersetzt (nicht aber hinzugefügt) werden.

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

Die folgenden Eigenschaften steuern die Festlegungsrichtlinie:

  • MemcacheService.SetPolicy.SET_ALWAYS: Der Wert wird hinzugefügt, wenn kein Wert mit dem Schlüssel vorhanden ist, oder der Wert ersetzt einen vorhandenen Wert, der den entsprechenden Schlüssel aufweist. Hierbei handelt es sich um die Standardmethode.
  • MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT: Der Wert wird hinzugefügt, wenn kein Wert mit dem Schlüssel vorhanden ist. Falls der Schlüssel bereits vorhanden ist, geschieht nichts.
  • MemcacheService.SetPolicy.REPLACE_ONLY_IF_PRESENT: Falls kein Wert mit dem entsprechenden Schlüssel vorhanden ist, geschieht nichts. Ein vorhandener Wert, der den Schlüssel aufweist, wird jedoch ersetzt.

Cachestatistiken abrufen

Die Anwendung kann Statistiken über ihre eigene Verwendung des Caches abrufen. Diese Statistiken sind bei der Überwachung und Feinabstimmung des Cache-Verhaltens hilfreich. Der Zugriff auf die Statistiken erfolgt über das CacheStatistics-Objekt, das Sie erhalten, wenn Sie die getCacheStatistics()-Methode des Caches aufrufen.

Die Statistiken beinhalten die Anzahl der Cache-Treffer (Abrufe für bereits vorhandene Schlüssel), die Anzahl der Cache-Fehler (Abrufe für Schlüssel, die noch nicht vorhanden waren) sowie die Anzahl der im Cache vorhandenen Werte.

import javax.cache.CacheStatistics;

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

Die App Engine-Implementierung unterstützt das Zurücksetzen der Zahlen hit und miss nicht. Diese Werte werden auf unbestimmte Zeit verwaltet, können aber aufgrund von vorübergehenden Bedingungen der Memcache-Server zurückgesetzt werden.

Memcache in der Google Cloud Console überwachen

  1. Rufen Sie die Memcache-Seite in der Google Cloud Console auf.
    Zur Seite „Memcache“
  2. Sehen Sie sich die folgenden Berichte an:
    • Memcache-Service-Level: Gibt an, ob Ihre Anwendung das Service Level „Freigegeben“ oder „Dediziert“ verwendet. Wenn Sie der Inhaber des Projekts sind, können Sie zwischen den beiden Service-Levels wechseln. Weitere Informationen über die Service Levels
    • Trefferquote: Gibt den Prozentsatz der Datenanfragen an, die aus dem Cache bereitgestellt wurden, sowie die reine Anzahl der Datenanfragen, die aus dem Cache bereitgestellt wurde.
    • Elemente im Cache
    • Alter des ältesten Elements: Das Alter des ältesten Elements im Cache. Das Alter eines Elements wird bei jedem daran ausgeführten Lese- oder Schreibvorgang zurückgesetzt.
    • Gesamtgröße des Cache
  3. Sie können eine der folgenden Aktionen ausführen:

    • Neuer Schlüssel: Fügen Sie dem Cache einen neuen Schlüssel hinzu.
    • Schlüssel suchen: Rufen Sie einen vorhandenen Schlüssel ab.
    • Cache leeren: Entfernen Sie alle Schlüssel/Wert-Paare aus dem Cache.
  4. (Nur dediziertes Memcache-Service-Level) Gehen Sie die Liste der Hot-Schlüssel durch.

    • „Hot-Schlüssel“ sind Schlüssel, die im Memcache mehr als 100 Abfragen pro Sekunde erhalten.
    • Diese Liste enthält bis zu 100 Hot-Schlüssel, sortiert nach der höchsten Abfrage pro Sekunde.

Gleichzeitige Schreibvorgänge verwalten

Wenn Sie den Wert eines Memcache-Schlüssels aktualisieren, der andere gleichzeitige Schreibanforderungen erhalten könnte, müssen Sie die Low-Level-Memcache-Methoden putIfUntouched und getIdentifiable statt put und get verwenden. Die Methoden putIfUntouched und getIdentifiable vermeiden Wettlaufbedingungen, da mehrere Anfragen, die gleichzeitig verarbeitet werden, den Wert desselben Memcache-Schlüssels in kleinstmöglichen Schritten aktualisieren.

Das folgende Code-Snippet zeigt eine Möglichkeit zum sicheren Aktualisieren des Werts eines Schlüssels, der gleichzeitig Aktualisierungsanfragen von anderen Clients erhalten kann.

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

Zur Optimierung des Beispielcodes können Sie die Anzahl der Wiederholungen begrenzen, um zu vermeiden, dass bei Ihrer App Engine-Anfrage eine Zeitüberschreitung infolge einer zu langen Blockierung auftritt.

Weitere Informationen