Kembangkan layanan caching umum untuk meningkatkan kinerja

Misalkan seorang rekan kerja meminta Anda daftar semua negara di dunia. Karena Anda bukan ahli geografi, Anda menjelajahi Situs Web Perserikatan Bangsa-Bangsa, mengunduh daftar, dan mencetaknya untuknya. Namun, dia hanya ingin memeriksa daftarnya; dia tidak benar-benar membawanya. Karena hal terakhir yang Anda butuhkan adalah selembar kertas lain di meja Anda, Anda memasukkan daftar itu ke mesin penghancur kertas.

Sehari kemudian, rekan kerja lain meminta hal yang sama: daftar setiap negara di dunia. Mengutuk diri sendiri karena tidak menyimpan daftar tersebut, Anda kembali membuka situs Perserikatan Bangsa-Bangsa. Pada kunjungan ke Situs Web ini, Anda perhatikan bahwa PBB memperbarui daftar negaranya setiap enam bulan. Anda mengunduh dan mencetak daftar untuk rekan kerja Anda. Dia melihatnya, terima kasih, dan sekali lagi, meninggalkan daftarnya bersama Anda. Kali ini Anda menyimpan daftar dengan pesan pada catatan tempel terlampir yang mengingatkan Anda untuk membuangnya setelah enam bulan.

Benar saja, selama beberapa minggu ke depan rekan kerja Anda terus meminta daftar tersebut lagi dan lagi. Anda memberi selamat kepada diri Anda sendiri karena telah mengarsipkan dokumen karena Anda dapat mengekstrak dokumen dari lemari arsip lebih cepat daripada Anda dapat mengekstraknya dari Situs Web. Konsep lemari arsip Anda menarik perhatian; segera semua orang mulai meletakkan barang di lemari Anda. Untuk mencegah kabinet menjadi tidak teratur, Anda menetapkan pedoman untuk menggunakannya. Dalam kapasitas resmi Anda sebagai manajer lemari arsip, Anda menginstruksikan rekan kerja Anda untuk menempatkan label dan catatan tempel pada semua dokumen, yang mengidentifikasi dokumen tersebut dan tanggal pembuangan / kedaluwarsanya. Label membantu rekan kerja Anda menemukan dokumen yang mereka cari, dan catatan tempel menentukan apakah informasi tersebut mutakhir.

Lemari arsip menjadi sangat populer sehingga Anda tidak dapat menyimpan dokumen baru di dalamnya. Anda harus memutuskan apa yang akan dibuang dan apa yang harus disimpan. Meskipun Anda membuang semua dokumen kadaluwarsa, lemari masih dipenuhi kertas. Bagaimana Anda memutuskan dokumen mana yang tidak perlu dibuang? Apakah Anda membuang dokumen terlama? Anda dapat membuang yang paling jarang digunakan atau yang paling jarang digunakan; dalam kedua kasus, Anda memerlukan log yang terdaftar saat setiap dokumen diakses. Atau mungkin Anda bisa memutuskan dokumen mana yang akan dibuang berdasarkan beberapa faktor penentu lainnya; keputusannya murni pribadi.

Untuk menghubungkan analogi dunia nyata di atas dengan dunia komputer, lemari arsip beroperasi sebagai cache: memori berkecepatan tinggi yang terkadang memerlukan pemeliharaan. Dokumen dalam cache adalah objek cache, yang semuanya sesuai dengan standar yang Anda tetapkan, pengelola cache. Proses pembersihan cache disebut pembersihan. Karena item yang di-cache dibersihkan setelah beberapa waktu berlalu, cache tersebut disebut cache yang berjangka waktu.

Di artikel ini, Anda akan mempelajari cara membuat cache Java murni 100 persen yang menggunakan utas latar belakang anonim untuk membersihkan item yang kedaluwarsa. Anda akan melihat bagaimana merancang cache semacam itu sambil memahami trade-off yang terlibat dengan berbagai desain.

Bangun cache

Analogi lemari arsip yang cukup: mari beralih ke Situs Web. Server situs web juga perlu menangani caching. Server berulang kali menerima permintaan informasi, yang identik dengan permintaan lainnya. Untuk tugas Anda selanjutnya, Anda harus membangun aplikasi Internet untuk salah satu perusahaan terbesar di dunia. Setelah empat bulan pengembangan, termasuk banyak malam tanpa tidur dan terlalu banyak Jolt cola, aplikasi memasuki pengujian pengembangan dengan 1.000 pengguna. Pengujian sertifikasi 5.000 pengguna dan peluncuran produksi 20.000 pengguna berikutnya mengikuti pengujian pengembangan. Namun, setelah Anda menerima kesalahan kehabisan memori sementara hanya 200 pengguna yang menguji aplikasi, pengujian pengembangan berhenti.

Untuk membedakan sumber penurunan kinerja, Anda menggunakan produk pembuatan profil dan menemukan bahwa server memuat banyak salinan database ResultSet, yang masing-masing memiliki beberapa ribu catatan. Catatan membuat daftar produk. Selain itu, daftar produk identik untuk setiap pengguna. Daftar tersebut tidak bergantung pada pengguna, seperti yang mungkin terjadi jika daftar produk dihasilkan dari kueri berparameter. Anda dengan cepat memutuskan bahwa satu salinan daftar dapat melayani semua pengguna bersamaan, jadi Anda menyimpannya dalam cache.

Namun, sejumlah pertanyaan muncul, yang meliputi kerumitan seperti:

  • Bagaimana jika daftar produk berubah? Bagaimana cache dapat membuat daftar kedaluwarsa? Bagaimana saya tahu berapa lama daftar produk harus tetap berada di cache sebelum kedaluwarsa?
  • Bagaimana jika ada dua daftar produk yang berbeda, dan kedua daftar tersebut berubah pada interval yang berbeda? Dapatkah saya mengakhiri setiap daftar satu per satu, atau haruskah semuanya memiliki umur simpan yang sama?
  • Bagaimana jika cache kosong dan dua pemohon mencoba cache pada saat yang sama? Ketika mereka berdua menemukannya kosong, apakah mereka akan membuat daftar mereka sendiri, dan kemudian keduanya mencoba untuk menyimpan salinan mereka ke dalam cache?
  • Bagaimana jika item berada di cache selama berbulan-bulan tanpa dapat diakses? Apakah mereka tidak akan memakan memori?

Untuk mengatasi tantangan ini, Anda perlu membangun layanan cache perangkat lunak.

Dalam analogi lemari arsip, orang selalu mengecek lemari terlebih dahulu saat mencari dokumen. Perangkat lunak Anda harus menerapkan prosedur yang sama: permintaan harus memeriksa layanan caching sebelum memuat daftar baru dari database. Sebagai pengembang perangkat lunak, tanggung jawab Anda adalah mengakses cache sebelum mengakses database. Jika daftar produk telah dimuat ke dalam cache, maka Anda menggunakan daftar cache, asalkan tidak kedaluwarsa. Jika daftar produk tidak ada di cache, maka Anda memuatnya dari database dan segera menyimpannya.

Catatan: Sebelum melanjutkan ke persyaratan dan kode layanan caching, Anda mungkin ingin melihat sidebar di bawah ini, "Caching Versus Pooling." Ini menjelaskan pooling, konsep terkait.

Persyaratan

Sesuai dengan prinsip desain yang baik, saya menetapkan daftar persyaratan untuk layanan caching yang akan kita kembangkan di artikel ini:

  1. Semua aplikasi Java dapat mengakses layanan caching.
  2. Objek dapat ditempatkan di cache.
  3. Objek dapat diekstraksi dari cache.
  4. Objek yang di-cache dapat menentukan sendiri saat kedaluwarsa, sehingga memungkinkan fleksibilitas maksimum. Layanan cache yang kedaluwarsa semua objek menggunakan rumus kedaluwarsa yang sama gagal memberikan penggunaan objek cache secara optimal. Pendekatan ini tidak memadai dalam sistem skala besar karena, misalnya, daftar produk dapat berubah setiap hari, sedangkan daftar lokasi toko mungkin berubah hanya sebulan sekali.
  5. Utas latar belakang yang berjalan di bawah prioritas rendah menghapus objek cache yang kedaluwarsa.
  6. Layanan caching dapat ditingkatkan nanti melalui penggunaan mekanisme pembersihan yang paling jarang digunakan (LRU) atau yang paling jarang digunakan (LFU).

Penerapan

Untuk memenuhi Persyaratan 1, kami mengadopsi lingkungan Java murni 100 persen. Dengan menyediakan publik getdan setmetode dalam layanan caching, kami memenuhi Persyaratan 2 dan 3 juga.

Sebelum melanjutkan dengan diskusi tentang Persyaratan 4, saya akan menyebutkan secara singkat bahwa kami akan memenuhi Persyaratan 5 dengan membuat utas anonim di pengelola cache; utas ini dimulai di blok statis. Selain itu, kami memenuhi Persyaratan 6 dengan mengidentifikasi titik-titik di mana kode nantinya akan ditambahkan untuk mengimplementasikan algoritma LRU dan LFU. Saya akan membahas lebih detail tentang persyaratan ini nanti di artikel.

Sekarang, kembali ke Persyaratan 4, di mana segala sesuatunya menjadi menarik. Jika setiap objek yang di-cache harus menentukan sendiri apakah itu kedaluwarsa, Anda harus memiliki cara untuk menanyakan objek apakah itu sudah kadaluwarsa. Artinya, semua objek di cache harus sesuai dengan aturan tertentu; Anda melakukannya di Java dengan mengimplementasikan antarmuka.

Mari kita mulai dengan aturan yang mengatur objek yang ditempatkan di cache.

  1. Semua objek harus memiliki metode publik yang dipanggil isExpired(), yang mengembalikan nilai Boolean.
  2. Semua objek harus memiliki metode publik yang dipanggil getIdentifier(), yang mengembalikan objek yang membedakan objek dari semua yang lain di cache.

Catatan: Sebelum langsung beralih ke kode, Anda harus memahami bahwa Anda dapat menerapkan cache dengan banyak cara. Saya telah menemukan lebih dari selusin implementasi yang berbeda. Enhydra dan Caucho menyediakan sumber daya yang sangat baik yang berisi beberapa implementasi cache.

Anda akan menemukan kode antarmuka untuk layanan cache artikel ini di Listing 1.

Daftar 1. Cacheable.java

/ ** * Judul: Caching Deskripsi: Antarmuka ini mendefinisikan metode, yang harus diterapkan oleh semua objek yang ingin ditempatkan di cache. * * Hak Cipta: Hak Cipta (c) 2001 * Perusahaan: JavaWorld * Nama File: Cacheable.java @author Jonathan Lurie @version 1.0 * / antarmuka publik Dapat disimpan dalam cache {/ * Dengan meminta semua objek untuk menentukan masa berlakunya sendiri, algoritme diambil dari layanan caching, sehingga memberikan fleksibilitas maksimum karena setiap objek dapat mengadopsi strategi kedaluwarsa yang berbeda. * / public boolean isExpired (); / * Metode ini akan memastikan bahwa layanan caching tidak bertanggung jawab untuk mengidentifikasi objek yang ditempatkan di cache secara unik. * / public Object getIdentifier (); }

Objek apa pun yang ditempatkan di cache - a String, misalnya - harus dibungkus di dalam objek yang mengimplementasikan Cacheableantarmuka. Kode 2 adalah contoh dari kelas pembungkus generik yang disebut CachedObject; itu dapat berisi objek apa pun yang perlu ditempatkan di layanan cache. Perhatikan bahwa kelas pembungkus ini mengimplementasikan Cacheableantarmuka yang ditentukan dalam Kode 1.

Daftar 2. CachedManagerTestProgram.java

/ ** * Judul: Caching * Deskripsi: Pembungkus Objek Cache Generik. Menerapkan antarmuka Cacheable * menggunakan status TimeToLive untuk kedaluwarsa CacheObject. * Hak Cipta: Hak Cipta (c) 2001 * Perusahaan: JavaWorld * Nama file: CacheManagerTestProgram.java * @author Jonathan Lurie * @version 1.0 * / kelas publik CachedObject mengimplementasikan Cacheable {// +++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++++++++ ++++ / * Variabel ini akan digunakan untuk menentukan apakah objek kedaluwarsa. * / private java.util.Date dateofExpiration = null; private Object identifier = null; / * Ini berisi "nilai" yang sebenarnya. Inilah objek yang perlu dibagikan. * / objek objek publik = null; // +++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++ publik CachedObject (Objek obj, id Objek, int minutesToLive) {this.object = obj; ini.pengenal = id; // minutesToLive dari 0 berarti ia hidup tanpa batas. if (minutesToLive! = 0) {dateofExpiration = new java.util.Date (); java.util.Calendar cal = java.util.Calendar.getInstance (); cal.setTime (dateofExpiration); cal.add (cal.MINUTE, minutesToLive); dateofExpiration = cal.getTime (); }} // ++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++ boolean publik isExpired () {// Ingatlah jika menit untuk hidup adalah nol maka hidup selamanya! if (dateofExpiration! = null) {// tanggal kedaluwarsa dibandingkan. if (dateofExpiration.before (new java.util.Date ())) {System.out.println ("CachedResultSet.isExpired: Kedaluwarsa dari Cache! EXPIRE TIME:" + dateofExpiration.toString () + "CURRENT TIME:" + ( new java.util.Date ()). toString ()); kembali benar; } lain {System.out.println ("CachedResultSet.isExpired:Kedaluwarsa bukan dari Cache! "); Return false;}} else // Artinya hidup selamanya! Return false;} // ++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++++ Objek publik getIdentifier () {return identifier;} // ++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++}

The CachedObjectkelas mengekspos metode konstruktor yang mengambil tiga parameter:

public CachedObject (Object obj, Object id, int minutesToLive) 

Tabel di bawah menjelaskan parameter tersebut.

Deskripsi parameter konstruktor CachedObject
Nama Tipe Deskripsi
Obj Obyek Objek yang dibagikan. Ini didefinisikan sebagai objek untuk memungkinkan fleksibilitas maksimum.
Id Obyek Idberisi pengenal unik yang membedakan objparameter dari semua objek lain yang berada di cache. Layanan caching tidak bertanggung jawab untuk memastikan keunikan objek dalam cache.
minutesToLive Int The number of minutes that the obj parameter is valid in the cache. In this implementation, the caching service interprets a value of zero to mean that the object never expires. You might want to change this parameter in the event that you need to expire objects in less than one minute.

The constructor method determines the expiration date of the object in the cache using a time-to-live strategy. As its name implies, time-to-live means that a certain object has a fixed time at the conclusion of which it is considered dead. By adding minutesToLive, the constructor's int parameter, to the current time, an expiration date is calculated. This expiration is assigned to the class variable dateofExpiration.

Sekarang, isExpired()metode tersebut harus menentukan apakah dateofExpirationsebelum atau sesudah tanggal dan waktu saat ini. Jika tanggal sebelum waktu sekarang, dan objek yang di-cache dianggap kedaluwarsa, isExpired()metode akan mengembalikan nilai true; jika tanggal setelah waktu saat ini, objek yang di-cache tidak kedaluwarsa, dan isExpired()mengembalikan false. Tentu saja, jika dateofExpirationnol, yang akan menjadi kasus jika minutesToLivenol, maka isExpired()metode selalu mengembalikan salah, menunjukkan bahwa objek yang di-cache hidup selamanya.