---
title: "Keajaiban komunikasi antara worker NGINX: salah satu struktur data terpenting `shared dict` - API7.ai"
description: "shared dict adalah struktur data penting dalam NGINX dan OpenResty."
url: "https://api7.ai/id/learning-center/openresty/communication-magic-between-nginx-workers-shared-dict"
---

# Keajaiban komunikasi antara worker NGINX: salah satu struktur data terpenting \`shared dict\`

API7.ai

October 27, 2022

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

Seperti yang telah kami katakan dalam artikel sebelumnya, `table` adalah satu-satunya struktur data di Lua. Ini sesuai dengan `shared dict`, yang merupakan struktur data paling penting yang dapat Anda gunakan dalam pemrograman OpenResty. Ini mendukung penyimpanan data, pembacaan, penghitungan atomik, dan operasi antrian.

Berdasarkan `shared dict`, Anda dapat mengimplementasikan caching dan komunikasi antara beberapa `Worker`, serta pembatasan laju, statistik lalu lintas, dan fungsi lainnya. Anda dapat menggunakan `shared dict` sebagai Redis sederhana, kecuali bahwa data dalam `shared dict` tidak persisten, sehingga Anda harus mempertimbangkan kehilangan data yang disimpan.

## Beberapa cara berbagi data

Dalam menulis kode Lua OpenResty, Anda pasti akan menemui berbagi data antara `Worker` yang berbeda dalam berbagai fase permintaan. Anda mungkin juga perlu berbagi data antara kode Lua dan C.

Jadi, sebelum kami secara resmi memperkenalkan API `shared dict`, mari kita pahami terlebih dahulu metode berbagi data umum di OpenResty dan belajar bagaimana memilih metode berbagi data yang lebih sesuai sesuai dengan situasi saat ini.

**Pertama adalah variabel di NGINX.** Ini dapat berbagi data antara modul C NGINX. Secara alami, ini juga dapat berbagi data antara modul C dan `lua-nginx-module` yang disediakan oleh OpenResty, seperti dalam kode berikut.

```
location /foo {
     set $my_var ''; # baris ini diperlukan untuk membuat $my_var pada waktu konfigurasi
     content_by_lua_block {
         ngx.var.my_var = 123;
         ...
     }
 }
```

Namun, menggunakan variabel NGINX untuk berbagi data lambat karena melibatkan pencarian hash dan alokasi memori. Juga, pendekatan ini memiliki batasan bahwa ini hanya dapat digunakan untuk menyimpan string dan tidak dapat mendukung tipe Lua yang kompleks.

**Kedua adalah `ngx.ctx`, yang dapat berbagi data antara fase yang berbeda dari permintaan yang sama.** Ini adalah `table` Lua biasa, sehingga cepat dan dapat menyimpan berbagai objek Lua. Siklus hidupnya adalah tingkat permintaan; ketika permintaan berakhir, `ngx.ctx` dihancurkan.

Berikut adalah skenario penggunaan tipikal di mana kami menggunakan `ngx.ctx` untuk cache panggilan yang mahal seperti variabel NGINX dan menggunakannya di berbagai tahap.

```
location /test {
     rewrite_by_lua_block {
         ngx.ctx.host = ngx.var.host
     }
     access_by_lua_block {
        if (ngx.ctx.host == 'api7.ai') then
            ngx.ctx.host = 'test.com'
        end
     }
     content_by_lua_block {
         ngx.say(ngx.ctx.host)
     }
 }
```

Dalam kasus ini, jika Anda menggunakan `curl` untuk mengaksesnya.

```
curl -i 127.0.0.1:8080/test -H 'host:api7.ai'
```

Maka ini akan mencetak `test.com`, menunjukkan bahwa `ngx.ctx` berbagi data di berbagai tahap. Tentu saja, Anda juga dapat memodifikasi contoh di atas dengan menyimpan objek yang lebih kompleks seperti `table` alih-alih string sederhana untuk melihat apakah ini memenuhi harapan Anda.

Namun, catatan khusus di sini adalah karena siklus hidup `ngx.ctx` adalah tingkat permintaan, ini tidak melakukan cache di tingkat modul. Misalnya, saya melakukan kesalahan dengan menggunakan ini di file `foo.lua` saya.

```
local ngx_ctx = ngx.ctx

local function bar()
    ngx_ctx.host =  'test.com'
end
```

Kita harus memanggil dan melakukan cache di tingkat fungsi.

```
local ngx = ngx

local function bar()
    ngx_ctx.host =  'test.com'
end
```

Ada banyak detail lagi tentang `ngx.ctx`, yang akan kita lanjutkan eksplorasinya nanti di bagian optimasi kinerja.

**Pendekatan ketiga menggunakan variabel tingkat modul untuk berbagi data di semua permintaan dalam `Worker` yang sama.** Tidak seperti variabel NGINX dan `ngx.ctx` sebelumnya, pendekatan ini sedikit kurang dapat dipahami. Tapi jangan khawatir, konsepnya abstrak, dan kode datang terlebih dahulu, jadi mari kita lihat contoh untuk memahami variabel tingkat modul.

```
-- mydata.lua
local _M = {}

local data = {
    dog = 3,
    cat = 4,
    pig = 5,
}

function _M.get_age(name)
    return data[name]
end

return _M
```

Konfigurasi di `nginx.conf` adalah sebagai berikut.

```
location /lua {
     content_by_lua_block {
         local mydata = require "mydata"
         ngx.say(mydata.get_age("dog"))
     }
 }
```

Dalam contoh ini, `mydata` adalah modul yang hanya dimuat sekali oleh proses `Worker`, dan semua permintaan yang diproses oleh `Worker` setelah itu berbagi kode dan data dari modul `mydata`.

Secara alami, variabel data dalam modul `mydata` adalah variabel tingkat modul yang terletak di tingkat atas modul, yaitu di awal modul, dan dapat diakses oleh semua fungsi.

Jadi, Anda dapat menempatkan data yang perlu dibagikan antara permintaan dalam variabel tingkat atas modul. Namun, penting untuk dicatat bahwa kita umumnya hanya menggunakan cara ini untuk menyimpan data yang hanya dibaca. Jika operasi penulisan terlibat, Anda harus sangat berhati-hati karena mungkin ada `race condition`, yang merupakan bug yang sulit untuk dilacak.

Kita dapat mengalami ini dengan contoh yang paling disederhanakan berikut.

```
-- mydata.lua
local _M = {}

local data = {
    dog = 3,
    cat = 4,
    pig = 5,
}

function _M.incr_age(name)
    data[name]  = data[name] + 1
    return data[name]
end

return _M
```

Dalam modul, kita menambahkan fungsi `incr_age`, yang memodifikasi data dalam tabel `data`.

Kemudian, dalam kode pemanggilan, kita menambahkan baris paling kritis `ngx.sleep(5)`, di mana `sleep` adalah operasi `yield`.

```
location /lua {
     content_by_lua_block {
         local mydata = require "mydata"
         ngx.say(mydata. incr_age("dog"))
         ngx.sleep(5) -- yield API
         ngx.say(mydata. incr_age("dog"))
     }
 }
```

Tanpa baris kode `sleep` ini (atau operasi IO non-blocking lainnya, seperti mengakses Redis, dll.), tidak akan ada operasi yield, tidak ada persaingan, dan output akhir akan berurutan.

Tetapi ketika kita menambahkan baris kode ini, bahkan jika hanya dalam 5 detik tidur, permintaan lain kemungkinan akan memanggil fungsi `mydata.incr_age` dan memodifikasi nilai variabel, sehingga menyebabkan angka output akhir tidak berurutan. Logikanya tidak sesederhana itu dalam kode aktual, dan bug jauh lebih sulit untuk dilacak.

Jadi, kecuali Anda yakin tidak ada operasi `yield` di antara yang akan memberikan kontrol ke loop event NGINX, saya sarankan untuk menjaga variabel tingkat modul Anda hanya untuk dibaca.

Pendekatan keempat dan terakhir menggunakan `shared dict` untuk berbagi data yang dapat dibagikan di antara beberapa worker.

Pendekatan ini didasarkan pada implementasi pohon merah-hitam, yang berkinerja baik. Namun, ini memiliki batasannya: Anda harus mendeklarasikan ukuran memori bersama dalam file konfigurasi NGINX sebelumnya, dan ini tidak dapat diubah saat runtime:

```
lua_shared_dict dogs 10m;
```

`Shared dict` juga hanya melakukan cache data `string` dan tidak mendukung tipe data Lua yang kompleks. Ini berarti bahwa ketika saya perlu menyimpan tipe data kompleks seperti `table`, saya harus menggunakan JSON atau metode lain untuk melakukan serialisasi dan deserialisasi, yang secara alami akan menyebabkan banyak kehilangan kinerja.

Bagaimanapun, tidak ada solusi sempurna di sini, dan tidak ada cara yang sempurna untuk berbagi data. Anda harus menggabungkan beberapa metode sesuai dengan kebutuhan dan skenario Anda.

## Shared dict

Kami telah menghabiskan banyak waktu untuk mempelajari bagian berbagi data di atas, dan beberapa dari Anda mungkin bertanya-tanya: sepertinya mereka tidak langsung terkait dengan shared dict. Apakah itu tidak sesuai topik?

Sebenarnya, tidak. Silakan pikirkan: mengapa ada `shared dict` di OpenResty? Ingatlah bahwa tiga metode pertama berbagi data semuanya berada di tingkat permintaan atau tingkat `Worker` individu. Oleh karena itu, dalam implementasi OpenResty saat ini, hanya `shared dict` yang dapat menyelesaikan berbagi data antara `Worker`, memungkinkan komunikasi antara `Worker`, yang merupakan nilai keberadaannya.

Menurut saya, memahami mengapa teknologi ada dan mengetahui perbedaan dan keunggulannya dibandingkan dengan teknologi serupa lainnya jauh lebih penting daripada hanya mahir memanggil API yang disediakannya. Visi teknis ini memberi Anda tingkat wawasan dan pemahaman yang mendalam, dan bisa dibilang merupakan perbedaan penting antara insinyur dan arsitek.

Kembali ke shared dict, yang menyediakan lebih dari 20 API Lua untuk umum, semuanya bersifat atomik, sehingga Anda tidak perlu khawatir tentang persaingan dalam kasus beberapa `Worker` dan konkurensi tinggi.

API ini semua memiliki [dokumen resmi](https://github.com/openresty/lua-nginx-module#ngxshareddict) yang detail, jadi saya tidak akan membahas semuanya. Saya ingin menekankan sekali lagi bahwa tidak ada kursus teknis yang dapat menggantikan pembacaan dokumen resmi dengan cermat. Tidak ada yang bisa melewatkan prosedur yang memakan waktu dan bodoh ini.

Selanjutnya, mari kita lanjutkan melihat API `shared dict`, yang dapat dibagi menjadi tiga kategori: baca/tulis dict, operasi antrian, dan manajemen.

### Baca/tulis dict

Mari kita lihat terlebih dahulu kelas baca dan tulis dict. Dalam versi aslinya, hanya ada API untuk kelas baca dan tulis dict, fitur paling umum dari kamus bersama. Berikut adalah contoh paling sederhana.

```
$ resty --shdict='dogs 1m' -e 'local dict = ngx.shared.dogs
                               dict:set("Tom", 56)
                               print(dict:get("Tom"))'
```

Selain `set`, OpenResty juga menyediakan empat metode penulisan: `safe_set`, `add`, `safe_add`, dan `replace`. Arti dari awalan `safe` di sini adalah bahwa jika memori penuh, alih-alih menghapus data lama sesuai dengan LRU, penulisan akan gagal dan mengembalikan kesalahan `no memory`.

Selain `get`, OpenResty juga menyediakan metode `get_stale` untuk membaca data, yang memiliki nilai kembalian tambahan untuk data yang kedaluwarsa dibandingkan dengan metode `get`.

```
value, flags, stale = ngx.shared.DICT:get_stale(key)
```

Anda juga dapat memanggil metode `delete` untuk menghapus kunci yang ditentukan, yang setara dengan `set(key, nil)`.

### Operasi Antrian

Berbicara tentang operasi antrian, ini adalah tambahan yang lebih baru di OpenResty yang menyediakan antarmuka yang mirip dengan Redis. Setiap elemen dalam antrian dijelaskan oleh `ngx_http_lua_shdict_list_node_t`.

```
typedef struct {
    ngx_queue_t queue;
    uint32_t value_len;
    uint8_t value_type;
    u_char data[1];
} ngx_http_lua_shdict_list_node_t;
```

Saya telah memposting [PR](https://github.com/openresty/lua-nginx-module/pull/586/files) dari API antrian ini di artikel. Jika Anda tertarik dengan ini, Anda dapat mengikuti dokumentasi, kasus uji, dan kode sumber untuk menganalisis implementasi spesifik.

Namun, tidak ada contoh kode yang sesuai untuk lima API antrian berikut dalam dokumentasi, jadi saya akan memperkenalkannya secara singkat di sini.

- `lpush``/``rpush` berarti menambahkan elemen di kedua ujung antrian.
- `lpop``/``rpop`, yang mengeluarkan elemen di kedua ujung antrian.
- `llen`, yang menunjukkan jumlah elemen yang dikembalikan ke antrian.

Jangan lupa alat berguna lain yang kita bahas di artikel terakhir: kasus uji. Kita biasanya dapat menemukan kode yang sesuai dalam kasus uji jika tidak ada dalam dokumentasi. Tes yang terkait dengan antrian tepatnya ada di file `145-shdict-list.t`.

```
=== TEST 1: lpush & lpop
--- http_config
    lua_shared_dict dogs 1m;
--- config
    location = /test {
        content_by_lua_block {
            local dogs = ngx.shared.dogs

            local len, err = dogs:lpush("foo", "bar")
            if len then
                ngx.say("push success")
            else
                ngx.say("push err: ", err)
            end

            local val, err = dogs:llen("foo")
            ngx.say(val, " ", err)

            local val, err = dogs:lpop("foo")
            ngx.say(val, " ", err)

            local val, err = dogs:llen("foo")
            ngx.say(val, " ", err)

            local val, err = dogs:lpop("foo")
            ngx.say(val, " ", err)
        }
    }
--- request
GET /test
--- response_body
push success
1 nil
bar nil
0 nil
nil nil
--- no_error_log
[error]
```

### Manajemen

API manajemen terakhir juga merupakan tambahan yang lebih baru dan merupakan permintaan populer di komunitas. Salah satu contoh paling tipikal adalah penggunaan memori bersama. Misalnya, jika seorang pengguna meminta ruang `100M` sebagai `shared dict`, apakah `100M` ini cukup? Berapa banyak kunci yang disimpan di dalamnya, dan kunci apa saja itu? Ini semua adalah pertanyaan yang otentik.

Untuk jenis masalah ini, OpenResty resmi berharap pengguna menggunakan grafik api untuk menyelesaikannya, yaitu dengan cara non-invasif, menjaga basis kode tetap efisien dan rapi, alih-alih menyediakan API invasif untuk mengembalikan hasil secara langsung.

Tetapi dari sudut pandang ramah pengguna, API manajemen ini masih sangat penting. Lagi pula, proyek open source dirancang untuk memenuhi kebutuhan produk, bukan untuk memamerkan teknologi itu sendiri. Jadi, mari kita lihat API manajemen berikut yang akan ditambahkan nanti.

Pertama adalah `get_keys(max_count?)`, yang secara default hanya mengembalikan `1024` kunci pertama; jika Anda mengatur `max_count` ke `0`, ini akan mengembalikan semua kunci. Kemudian datang `capacity` dan `free_space`, keduanya adalah bagian dari repositori `lua-resty-core`, jadi Anda perlu `require` mereka sebelum menggunakannya.

```
require "resty.core.shdict"

local cats = ngx.shared.cats
local capacity_bytes = cats:capacity()
local free_page_bytes = cats:free_space()
```

Mereka mengembalikan ukuran memori bersama (ukuran yang dikonfigurasi dalam `lua_shared_dict`) dan jumlah byte halaman bebas. Karena `shared dict` dialokasikan per halaman, bahkan jika `free_space` mengembalikan `0`, mungkin masih ada ruang di halaman yang dialokasikan. Oleh karena itu, nilai kembalinya tidak mewakili berapa banyak memori bersama yang digunakan.

## Ringkasan

Dalam praktiknya, kita sering menggunakan caching multi-level, dan proyek OpenResty resmi juga memiliki paket caching. Bisakah Anda menemukan proyek mana mereka? Atau apakah Anda tahu beberapa perpustakaan `lua-resty` lain yang mengemas caching?

Anda dipersilakan untuk membagikan artikel ini dengan rekan dan teman Anda sehingga kita dapat berkomunikasi dan meningkatkan 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

- [Beberapa cara berbagi data](#beberapa-cara-berbagi-data)

- [Shared dict](#shared-dict)

  - [Baca/tulis dict](#bacatulis-dict)
  - [Operasi Antrian](#operasi-antrian)
  - [Manajemen](#manajemen)

- [Ringkasan](#ringkasan)
