---
title: "`lua-resty-*` Enkapsulasi Membebaskan Pengembang dari Multi-Level Caching - API7.ai"
description: "This article introduces the implementation principles and usage of two cache encapsulation libraries `lua-resty-memcached-shdict` and `lua-resty-mlcache`"
url: "https://api7.ai/id/learning-center/openresty/lua-resty-encapsulation-releases-developers-from-caching"
---

# \`lua-resty-\*\` Enkapsulasi Membebaskan Pengembang dari Multi-Level Caching

API7.ai

December 30, 2022

[OpenResty (NGINX + Lua)](https://api7.ai/learning-center/openresty)

Dalam dua artikel sebelumnya, kita telah mempelajari tentang caching di OpenResty dan masalah cache stampede, yang semuanya berada di sisi dasar. Dalam pengembangan proyek aktual, para pengembang lebih memilih library yang siap pakai dengan semua detail yang ditangani dan disembunyikan, sehingga dapat digunakan langsung untuk mengembangkan kode bisnis.

Ini adalah manfaat dari pembagian tugas, di mana pengembang komponen dasar fokus pada arsitektur yang fleksibel, kinerja yang baik, dan stabilitas kode tanpa perlu memikirkan logika bisnis tingkat atas; sementara insinyur aplikasi lebih memperhatikan implementasi bisnis dan iterasi cepat, berharap tidak terganggu oleh berbagai detail teknis dari lapisan bawah. Celah di antara keduanya dapat diisi oleh library wrapper.

Caching di OpenResty menghadapi masalah yang sama. `shared dict` dan `lru caches` cukup stabil dan efisien, tetapi ada terlalu banyak detail yang harus ditangani. "Mil terakhir" bagi insinyur pengembangan aplikasi bisa sangat sulit tanpa beberapa enkapsulasi yang berguna. Di sinilah pentingnya komunitas berperan. Komunitas yang aktif akan secara proaktif menemukan celah dan mengisinya dengan cepat.

## `lua-resty-memcached-shdict`

Mari kembali ke enkapsulasi cache. `lua-resty-memcached-shdict` adalah proyek resmi OpenResty yang menggunakan `shared dict` untuk membuat lapisan enkapsulasi untuk `memcached`, menangani detail seperti cache stampede dan data yang kedaluwarsa. Jika data cache Anda kebetulan disimpan di `memcached` di backend, maka Anda dapat mencoba menggunakan library ini.

Ini adalah library yang dikembangkan oleh OpenResty, tetapi tidak termasuk dalam paket OpenResty secara default. Jika Anda ingin mengujinya secara lokal, Anda perlu mengunduh [source code](https://github.com/openresty/lua-resty-memcached-shdict) terlebih dahulu ke jalur pencarian OpenResty lokal.

Library enkapsulasi ini adalah solusi yang sama yang kami sebutkan dalam artikel sebelumnya. Ini menggunakan `lua-resty-lock` untuk saling eksklusif. Jika cache gagal, hanya satu permintaan yang akan pergi ke `memcached` untuk mengambil data dan menghindari badai cache. Data yang sudah kedaluwarsa akan dikembalikan ke endpoint jika data terbaru tidak berhasil diambil.

Namun, library `lua-resty` ini, meskipun merupakan proyek resmi OpenResty, tidak sempurna:

1. Pertama, tidak ada cakupan kasus uji, yang berarti kualitas kode tidak dapat dijamin secara konsisten.
2. Kedua, terlalu banyak parameter antarmuka yang diekspos, dengan 11 parameter yang diperlukan dan 7 parameter opsional.

```
local memc_fetch, memc_store =
    shdict_memc.gen_memc_methods{
        tag = "my memcached server tag",
        debug_logger = dlog,
        warn_logger = warn,
        error_logger = error_log,

        locks_shdict_name = "some_lua_shared_dict_name",

        shdict_set = meta_shdict_set,  
        shdict_get = meta_shdict_get,  

        disable_shdict = false,  -- optional, default false

        memc_host = "127.0.0.1",
        memc_port = 11211,
        memc_timeout = 200,  -- in ms
        memc_conn_pool_size = 5,
        memc_fetch_retries = 2,  -- optional, default 1
        memc_fetch_retry_delay = 100, -- in ms, optional, default to 100 (ms)

        memc_conn_max_idle_time = 10 * 1000,  -- in ms, for in-pool connections,optional, default to nil

        memc_store_retries = 2,  -- optional, default to 1
        memc_store_retry_delay = 100,  -- in ms, optional, default to 100 (ms)

        store_ttl = 1,  -- in seconds, optional, default to 0 (i.e., never expires)
    }
```

Sebagian besar parameter yang diekspos dapat disederhanakan dengan "membuat handler `memcached` baru". Cara enkapsulasi saat ini dengan melemparkan semua parameter kepada pengguna tidak ramah pengguna, jadi saya akan menyambut baik pengembang yang tertarik untuk berkontribusi PR untuk mengoptimalkan ini.

Selain itu, dokumentasi library enkapsulasi ini menyebutkan beberapa arah optimasi lebih lanjut.

1. Gunakan `lua-resty-lrucache` untuk meningkatkan cache tingkat `Worker`, bukan hanya cache tingkat `server` dengan `shared dict`.
2. Gunakan `ngx.timer` untuk melakukan operasi pembaruan cache secara asinkron.

Arah pertama adalah saran yang sangat baik, karena kinerja cache dalam worker lebih baik; saran kedua adalah sesuatu yang perlu Anda pertimbangkan berdasarkan skenario aktual Anda. Namun, saya umumnya tidak merekomendasikan yang kedua, tidak hanya karena ada batasan jumlah timer, tetapi juga karena jika logika pembaruan di sini salah, cache tidak akan pernah diperbarui lagi, yang memiliki dampak besar.

## `lua-resty-mlcache`

Selanjutnya, mari kita perkenalkan enkapsulasi caching yang umum digunakan di OpenResty: `lua-resty-mlcache`, yang menggunakan `shared dict` dan `lua-resty-lrucache` untuk mengimplementasikan mekanisme caching multi-layer. Mari kita lihat bagaimana library ini digunakan dalam dua contoh kode berikut.

```
local mlcache = require "resty.mlcache"

local cache, err = mlcache.new("cache_name", "cache_dict", {
    lru_size = 500,    -- ukuran cache L1 (Lua VM)
    ttl = 3600,   -- 1h ttl untuk hits
    neg_ttl  = 30,     -- 30s ttl untuk misses
})
if not cache then
    error("failed to create mlcache: " .. err)
end
```

Mari kita lihat potongan kode pertama. Awal kode ini memperkenalkan library `mlcache` dan mengatur parameter untuk inisialisasi. Biasanya kita akan menempatkan kode ini di fase `init` dan hanya perlu melakukannya sekali.

Selain dua parameter yang diperlukan, nama cache dan nama dictionary, parameter ketiga adalah dictionary dengan 12 opsi yang bersifat opsional dan menggunakan nilai default jika tidak diisi. Ini jauh lebih elegan daripada `lua-resty-memcached-shdict`. Jika kita merancang antarmuka sendiri, lebih baik mengadopsi pendekatan `mlcache` - menjaga antarmuka sesederhana mungkin sambil mempertahankan fleksibilitas yang cukup.

Berikut adalah potongan kode kedua, yang merupakan kode logika saat permintaan diproses.

```
local function fetch_user(id)
    return db:query_user(id)
end

local id = 123
local user , err = cache:get(id , nil , fetch_user , id)
if err then
    ngx.log(ngx.ERR , "failed to fetch user: ", err)
    return
end

if user then
    print(user.id) -- 123
end
```

Seperti yang Anda lihat, cache multi-layer disembunyikan, jadi Anda perlu menggunakan objek `mlcache` untuk mengambil cache dan mengatur fungsi callback saat cache kedaluwarsa. Logika kompleks di balik ini dapat sepenuhnya disembunyikan.

Anda mungkin penasaran bagaimana library ini diimplementasikan secara internal. Selanjutnya, mari kita lihat arsitektur dan implementasi library ini. Gambar berikut adalah slide dari presentasi yang diberikan oleh Thibault Charbonnier, penulis `mlcache`, di OpenResty Con 2018.

![mlcache architecture](https://static.api7.ai/2022/12/08/6391afa6d43fb.webp?imageMogr2/format/webp)

Seperti yang dapat Anda lihat dari diagram, `mlcache` membagi data menjadi tiga lapisan, yaitu `L1`, `L2`, dan `L3`.

Cache `L1` adalah `lua-resty-lrucache`, di mana setiap `Worker` memiliki salinannya sendiri, dan dengan `N` `Worker`, ada `N` salinan data, sehingga ada redundansi data. Karena mengoperasikan `lrucache` dalam satu `Worker` tidak memicu kunci, ini memiliki kinerja yang lebih tinggi dan cocok sebagai cache tingkat pertama.

Cache `L2` adalah `shared dict`. Semua `Worker` berbagi satu salinan data cache dan akan mengkueri cache `L2` jika cache `L1` tidak mengenai. `ngx.shared`.DICT menyediakan API yang menggunakan spinlock untuk memastikan atomisitas operasi, jadi kita tidak perlu khawatir tentang kondisi balapan di sini.

`L3` adalah kasus di mana cache `L2` juga tidak mengenai, dan fungsi callback perlu dieksekusi untuk mengkueri sumber data, seperti database eksternal, dan kemudian menyimpannya ke `L2`. Di sini, untuk menghindari badai cache, ia menggunakan `lua-resty-lock` untuk memastikan hanya satu `Worker` yang pergi ke sumber data untuk mengambil data.

Dari perspektif permintaan:

- Pertama, ia akan mengkueri cache `L1` dalam `Worker` dan mengembalikan langsung jika `L1` mengenai.
- Jika `L1` tidak mengenai atau cache gagal, ia mengkueri cache `L2` antar `Worker`. Jika `L2` mengenai, ia mengembalikan dan menyimpan hasilnya di `L1`.
- Jika `L2` juga tidak mengenai atau cache tidak valid, fungsi callback dipanggil untuk mencari data dari sumber data dan menuliskannya ke cache `L2`, yang merupakan fungsi dari lapisan data `L3`.

Anda juga dapat melihat dari proses ini bahwa pembaruan cache dipicu secara pasif oleh permintaan endpoint. Bahkan jika permintaan gagal mengambil cache, permintaan berikutnya masih dapat memicu logika pembaruan untuk memaksimalkan keamanan cache.

Namun, meskipun `mlcache` telah diimplementasikan dengan sempurna, masih ada satu titik sakit - serialisasi dan deserialisasi data. Ini bukan masalah dengan `mlcache`, tetapi perbedaan antara `lrucache` dan `shared dict`, yang kami sebutkan berulang kali. Di `lrucache`, kita dapat menyimpan berbagai tipe data Lua, termasuk `table`; tetapi di `shared dict`, kita hanya dapat menyimpan string.

L1, cache `lrucache`, adalah lapisan data yang disentuh pengguna, dan kita ingin menyimpan semua jenis data di dalamnya, termasuk `string`, `table`, `cdata`, dan sebagainya. Masalahnya adalah `L2` hanya dapat menyimpan string, dan ketika data dinaikkan dari `L2` ke `L1`, kita perlu melakukan konversi dari string ke tipe data yang dapat kita berikan langsung kepada pengguna.

Untungnya, `mlcache` telah mempertimbangkan situasi ini dan menyediakan fungsi opsional `l1_serializer` dalam antarmuka `new` dan `get`, yang dirancang khusus untuk menangani pemrosesan data saat `L2` dinaikkan ke `L1`. Kita dapat melihat kode contoh berikut, yang saya ekstrak dari set kasus uji saya.

```
local mlcache = require "resty.mlcache"

local cache, err = mlcache.new("my_mlcache", "cache_shm", {
l1_serializer = function(i)
    return i + 2
end,
})

local function callback()
    return 123456
end

local data = assert(cache:get("number", nil, callback))
assert(data == 123458)
```

Izinkan saya menjelaskannya dengan cepat. Dalam kasus ini, fungsi callback mengembalikan angka `123456`; di `new`, fungsi `l1_serializer` yang kita atur akan menambahkan `2` ke angka yang masuk sebelum mengatur cache `L1`, yang menjadi `123458`. Dengan fungsi serialisasi seperti ini, data dapat lebih fleksibel saat dikonversi antara `L1` dan `L2`.

## Ringkasan

Dengan beberapa lapisan caching, kinerja server dapat dimaksimalkan, dan banyak detail disembunyikan di antaranya. Pada titik ini, library wrapper yang stabil dan efisien menghemat banyak usaha kita. Saya juga berharap dua library wrapper yang diperkenalkan hari ini akan membantu Anda lebih memahami caching.

Terakhir, pikirkan pertanyaan ini: Apakah lapisan cache shared dictionary diperlukan? Apakah mungkin hanya menggunakan `lrucache`? Jangan ragu untuk meninggalkan komentar dan berbagi pendapat Anda dengan saya, dan Anda juga dipersilakan untuk membagikan artikel ini kepada lebih banyak orang untuk berkomunikasi dan berkembang bersama.

Share article link

Share

Related Posts

[![OpenResty FAQ | Bagaimana OpenResty Digunakan dalam Praktik](https://static.api7.ai/uploads/2023/02/12/jbv9M0zi_Hello%20World%20in%20OpenResty%20%283%29.png)](https://api7.ai/learning-center/openresty/faq-5)

## [OpenResty FAQ | Bagaimana OpenResty Digunakan dalam Praktik](https://api7.ai/learning-center/openresty/faq-5)

[![Bagian 3: Cara Membangun Microservices API Gateway Menggunakan OpenResty](https://static.api7.ai/uploads/2023/02/07/3XJXY9Jy_45.png)](https://api7.ai/learning-center/openresty/how-to-build-a-microservices-api-gateway-part-3)

## [Bagian 3: Cara Membangun Microservices API Gateway Menggunakan OpenResty](https://api7.ai/learning-center/openresty/how-to-build-a-microservices-api-gateway-part-3)

Table of Contents

- [`lua-resty-memcached-shdict`](#lua-resty-memcached-shdict)
- [`lua-resty-mlcache`](#lua-resty-mlcache)
- [Ringkasan](#ringkasan)
