Menggunakan Memcache

Halaman ini menjelaskan cara mengonfigurasi dan memantau layanan memcache untuk aplikasi Anda menggunakan konsol Google Cloud. Halaman ini juga menjelaskan cara melakukan tugas-tugas umum menggunakan antarmuka JCache dan cara menangani penulisan serentak menggunakan API memcache App Engine tingkat rendah untuk Java. Untuk mempelajari lebih lanjut tentang memcache, baca Ringkasan Memcache.

Mengonfigurasi memcache

  1. Buka halaman Memcache di konsol Google Cloud.
    Buka halaman Memcache
  2. Pilih tingkat layanan memcache yang ingin Anda gunakan:

    • Bersama (default) - gratis dan menyediakan kapasitas cache berdasarkan upaya terbaik.
    • Khusus - ditagih berdasarkan GB-jam ukuran cache dan menyediakan kapasitas cache tetap yang ditetapkan secara eksklusif untuk aplikasi Anda.

    Pelajari lebih lanjut tentang class layanan yang tersedia di Ringkasan Memcache.

Menggunakan JCache

SDK Java App Engine mendukung antarmuka JCache (JSR 107) untuk mengakses memcache. Antarmuka disertakan dalam paket javax.cache

Dengan JCache, Anda dapat menetapkan dan mendapatkan nilai, mengontrol akhir masa berlaku nilai di cache, memeriksa konten cache, serta mendapatkan statistik tentang cache. Anda juga dapat menggunakan "pemroses" untuk menambahkan perilaku kustom saat menetapkan dan menghapus nilai.

Implementasi App Engine mencoba menerapkan subset loyal dari standar API JCache. (Untuk informasi selengkapnya tentang JCache, lihat JSR 107.) Namun, daripada menggunakan JCache, sebaiknya gunakan API Memcache tingkat rendah untuk mengakses lebih banyak fitur layanan dasar.

Mendapatkan instance cache

Anda menggunakan penerapan antarmuka javax.cache.Cache untuk berinteraksi dengan cache. Anda mendapatkan instance Cache menggunakan CacheFactory, yang Anda peroleh dari metode statis pada CacheManager. Kode berikut mendapatkan instance Cache dengan konfigurasi default:

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

Metode createCache() CacheFactory mengambil Map properti konfigurasi. Properti ini dibahas di bawah. Untuk menerima setelan default, beri Map kosong kepada metode.

Menempatkan dan mendapatkan nilai

Cache berperilaku seperti Map: Anda menyimpan kunci dan nilai menggunakan metode put(), lalu mengambil nilai menggunakan metode get(). Anda dapat menggunakan objek Serializable untuk kunci atau nilainya.

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

Untuk memasukkan beberapa nilai, Anda dapat memanggil metode putAll() dengan Peta sebagai argumennya.

Untuk menghapus nilai dari cache (untuk segera mengeluarkannya), panggil metode remove() dengan kunci sebagai argumennya. Untuk menghapus setiap nilai dari cache untuk aplikasi, panggil metode clear().

Metode containsKey() mengambil kunci dan menampilkan boolean (true atau false) untuk menunjukkan apakah nilai dengan kunci tersebut ada dalam cache. Metode isEmpty() menguji apakah cache kosong. Metode size() menampilkan jumlah nilai yang saat ini ada dalam cache.

Mengonfigurasi akhir masa berlaku

Secara default, semua nilai tetap berada dalam cache selama mungkin, hingga dikeluarkan karena memori hampir penuh, dihapus secara eksplisit oleh aplikasi, atau dibuat tidak tersedia karena alasan lain (seperti pemadaman layanan). Aplikasi dapat menentukan waktu habis masa berlaku untuk nilai, dan jangka waktu maksimum nilai akan tersedia. Waktu habis masa berlaku dapat ditetapkan sebagai jumlah waktu yang relatif terhadap saat nilai ditetapkan, atau sebagai tanggal dan waktu absolut.

Anda dapat menentukan kebijakan akhir masa berlaku menggunakan properti konfigurasi saat membuat instance Cache. Semua nilai yang dimasukkan dengan instance tersebut menggunakan kebijakan akhir masa berlaku yang sama. Misalnya, untuk mengonfigurasi instance Cache ke nilai akhir masa berlaku satu jam (3.600 detik) setelah nilai ditetapkan:

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

Properti berikut mengontrol akhir masa berlaku nilai:

  • GCacheFactory.EXPIRATION_DELTA: mengakhiri masa berlaku nilai berdasarkan jumlah waktu yang diberikan relatif terhadap waktu saat nilai dimasukkan, sebagai bilangan bulat dalam hitungan detik
  • GCacheFactory.EXPIRATION_DELTA_MILLIS: mengakhiri masa berlaku nilai berdasarkan jumlah waktu yang diberikan relatif terhadap waktu saat nilai dimasukkan, sebagai bilangan bulat dalam hitungan milidetik
  • GCacheFactory.EXPIRATION: mengakhiri masa berlaku nilai berdasarkan tanggal dan waktu yang diberikan, sebagai java.util.Date

Mengonfigurasi kebijakan yang ditetapkan

Secara default, menetapkan nilai dalam cache akan menambahkan nilai jika tidak terdapat nilai pada kunci yang diberikan, dan menggantikan nilai jika terdapat nilai pada kunci yang diberikan. Anda dapat mengonfigurasi cache untuk hanya menambahkan (melindungi nilai yang ada) atau hanya mengganti nilai (tidak menambahkan).

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

Properti berikut mengontrol kebijakan yang ditetapkan:

  • MemcacheService.SetPolicy.SET_ALWAYS: menambahkan nilai jika tidak terdapat nilai pada kunci, mengganti nilai yang ada jika terdapat nilai pada kunci; ini adalah setelan default
  • MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT: menambahkan nilai jika tidak terdapat nilai pada kunci, tidak melakukan apa pun jika kunci tersebut ada
  • MemcacheService.SetPolicy.REPLACE_ONLY_IF_PRESENT: tidak melakukan apa pun jika tidak terdapat nilai pada kunci, mengganti nilai yang ada jika terdapat nilai pada kunci

Mengambil statistik cache

Aplikasi dapat mengambil statistik tentang penggunaan cache-nya sendiri. Statistik ini berguna untuk memantau dan menyesuaikan perilaku cache. Anda dapat mengakses statistik menggunakan objek CacheStatistics, yang didapatkan dengan memanggil metode getCacheStatistics() Cache.

Statistik yang tersedia menyertakan jumlah cache ditemukan (didapatkan untuk kunci yang ada), jumlah cache yang tidak ditemukan (didapatkan untuk kunci yang tidak ada), dan jumlah nilai dalam cache.

import javax.cache.CacheStatistics;

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

Implementasi App Engine tidak mendukung reset jumlah hit dan miss, yang dipertahankan tanpa batas waktu, tetapi dapat di-reset karena kondisi sementara dari server memcache.

Memantau memcache di konsol Google Cloud

  1. Buka halaman Memcache di konsol Google Cloud.
    Buka halaman Memcache
  2. Lihat laporan berikut:
    • Tingkat layanan Memcache: Menunjukkan apakah aplikasi Anda menggunakan tingkat layanan Bersama atau Khusus. Jika Anda adalah pemilik project, Anda dapat beralih di antara keduanya. Pelajari tingkat layanan lebih lanjut.
    • Rasio hit: Menampilkan persentase permintaan data yang disajikan dari cache, serta jumlah mentah permintaan data yang disajikan dari cache.
    • Item dalam cache.
    • Usia item terlama: Usia item terlama yang disimpan di cache. Perhatikan bahwa usia item akan di-reset setiap kali item digunakan, baik dibaca maupun ditulis.
    • Total ukuran cache.
  3. Anda dapat melakukan salah satu tindakan berikut:

    • Kunci baru: Menambahkan kunci baru ke cache.
    • Temukan kunci: Mengambil kunci yang ada.
    • Kosongkan cache: Menghapus semua key-value pair dari cache.
  4. (Hanya untuk memcache khusus) Lihat daftar Hotkey.

    • "Hotkey" adalah kunci yang menerima lebih dari 100 kueri per detik (QPS) di memcache.
    • Daftar ini berisi maksimal 100 hotkey, yang diurutkan berdasarkan QPS tertinggi.

Menangani penulisan serentak

Jika Anda memperbarui nilai kunci memcache yang mungkin menerima permintaan tulis serentak lainnya, Anda harus menggunakan metode memcache level rendah putIfUntouched dan getIdentifiable, bukan put dan get. Metode putIfUntouched dan getIdentifiable menghindari kondisi race dengan mengizinkan beberapa permintaan yang ditangani secara serentak untuk memperbarui nilai kunci memcache yang sama secara atomik.

Cuplikan kode di bawah ini menunjukkan satu cara untuk memperbarui nilai kunci secara aman yang mungkin memiliki permintaan pembaruan serentak dari klien lain:

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

Penajaman yang dapat Anda tambahkan ke kode contoh ini adalah dengan menetapkan batas jumlah percobaan ulang guna menghindari pemblokiran hingga waktu permintaan App Engine Anda habis.

Langkah berikutnya