Instans Tair (Redis OSS-compatible) mendukung perintah Lua. Skrip Lua dapat memproses perintah compare-and-set (CAS) secara efisien, meningkatkan performa instans, serta menyederhanakan implementasi fitur yang sebelumnya sulit atau tidak efisien untuk diimplementasikan. Topik ini menjelaskan sintaksis dan penggunaan skrip Lua.
Sintaksis
Praktik optimasi performa
Mengurangi overhead memori dan jaringan
Ketika banyak skrip dengan fungsi duplikat disimpan dalam cache di sebuah instans, mereka dapat mengonsumsi sejumlah besar memori dan bahkan menyebabkan kesalahan out-of-memory (OOM). Berikut adalah contoh penggunaan yang salah.
EVAL "return redis.call('set', 'k1', 'v1')" 0
EVAL "return redis.call('set', 'k2', 'v2')" 0Solusi:
Hindari melewatkan parameter ke skrip Lua sebagai konstanta untuk mengurangi penggunaan memori.
# Perintah berikut memiliki tujuan yang sama dengan perintah sampel sebelumnya tetapi hanya menyimpan skrip sekali. EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 k1 v1 EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 k2 v2Gunakan sintaksis perintah berikut untuk mengurangi overhead memori dan jaringan:
SCRIPT LOAD "return redis.call('set', KEYS[1], ARGV[1])" # Setelah perintah ini dijalankan, Redis mengembalikan "55b22c0d0cedf3866879ce7c854970626dcef0c3" EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k1 v1 EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k2 v2
Mengosongkan cache skrip Lua
Penggunaan memori instans mungkin lebih tinggi dari yang diperkirakan karena cache skrip Lua mengambil memori instans. Saat penggunaan memori mendekati atau melebihi batas atas, kesalahan OOM dapat terjadi. Contoh kesalahan:
-OOM command not allowed when used memory > 'maxmemory'.Solusi:
Kosongkan cache skrip Lua dengan menjalankan perintah SCRIPT FLUSH pada klien. Berbeda dengan perintah FLUSHALL, perintah SCRIPT FLUSH bersifat sinkron. Jika instans menyimpan banyak skrip Lua, perintah SCRIPT FLUSH dapat memblokir instans untuk periode waktu yang lama, sehingga instans tersebut mungkin menjadi tidak tersedia. Lanjutkan dengan hati-hati. Kami menyarankan Anda melakukan operasi ini selama jam-jam sepi.
Jika Anda mengklik Clear Data di konsol, data dapat dihapus tetapi cache skrip Lua tidak akan dikosongkan.
Hindari menulis skrip Lua yang mungkin menggunakan jumlah memori yang berlebihan. Selain itu, hindari menulis skrip Lua yang melibatkan sejumlah besar data. Jika tidak, penggunaan memori akan meningkat secara signifikan dan kesalahan OOM mungkin terjadi. Untuk mengurangi penggunaan memori, kami menyarankan Anda mengaktifkan penghapusan data (diaktifkan secara default untuk instans, dengan mode volatile-lru). Namun, instans tidak akan menghapus cache skrip Lua terlepas dari apakah penghapusan data diaktifkan.
Panduan penanganan kesalahan
Kesalahan NOSCRIPT
Jika skrip tidak disimpan dalam cache di instans saat menggunakan perintah EVALSHA, instans akan mengembalikan kesalahan NOSCRIPT. Contoh kesalahan:
(error) NOSCRIPT No matching script. Please use EVAL.Solusi:
Jalankan perintah EVAL atau SCRIPT LOAD untuk menyimpan skrip dalam cache di instans dan coba lagi. Dalam skenario tertentu, seperti migrasi instans dan perubahan konfigurasi, instans akan mengosongkan cache skrip Lua karena instans tidak dapat menjamin persistensi dan replikabilitas skrip Lua. Dalam hal ini, klien Anda harus dapat menangani kesalahan. Untuk informasi lebih lanjut, lihat Masalah Persistensi dan Replikasi.
Kode Python sampel berikut menunjukkan metode untuk menangani kesalahan NOSCRIPT. Kode sampel menambahkan string menggunakan skrip Lua.
Anda juga dapat menggunakan redis-py untuk menangani kesalahan ini. redis-py menyediakan kelas Script yang mengenkapsulasi logika penilaian untuk skrip Lua Redis, seperti pernyataan catch untuk kesalahan NOSCRIPT.
import redis
import hashlib
# strin adalah string dalam skrip Lua. Fungsi ini mengembalikan nilai sha1 dari strin dalam format string.
def calcSha1(strin):
sha1_obj = hashlib.sha1()
sha1_obj.update(strin.encode('utf-8'))
sha1_val = sha1_obj.hexdigest()
return sha1_val
class MyRedis(redis.Redis):
def __init__(self, host="localhost", port=6379, password=None, decode_responses=False):
redis.Redis.__init__(self, host=host, port=port, password=password, decode_responses=decode_responses)
def prepend_inLua(self, key, value):
script_content = """\
local suffix = redis.call("get", KEYS[1])
local prefix = ARGV[1]
local new_value = prefix..suffix
return redis.call("set", KEYS[1], new_value)
"""
script_sha1 = calcSha1(script_content)
if self.script_exists(script_sha1)[0] == True: # Periksa apakah skrip sudah disimpan dalam cache di Redis.
return self.evalsha(script_sha1, 1, key, value) # Jika skrip sudah disimpan, perintah EVALSHA digunakan untuk menjalankan skrip.
else:
return self.eval(script_content, 1, key, value) # Jika tidak, gunakan perintah EVAL untuk menjalankan skrip. Perhatikan bahwa perintah EVAL dapat menyimpan skrip dalam cache di Redis. Anda juga dapat menggunakan perintah SCRIPT LOAD dan EVALSHA.
r = MyRedis(host="r-******.redis.rds.aliyuncs.com", password="***:***", port=6379, decode_responses=True)
print(r.prepend_inLua("k", "v"))
print(r.get("k"))
Kesalahan timeout skrip Lua
Permintaan Lua yang lambat dapat memblokir instans karena skrip Lua dieksekusi secara atomik di instans. Satu skrip Lua dapat memblokir instans hingga 5 detik. Setelah 5 detik, instans mengembalikan kesalahan BUSY untuk perintah lainnya hingga eksekusi skrip selesai.
BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.Solusi:
Jalankan perintah SCRIPT KILL untuk menghentikan skrip Lua atau tunggu hingga eksekusi skrip Lua selesai.
CatatanSelama 5 detik pertama ketika skrip Lua yang lambat sedang dieksekusi, perintah SCRIPT KILL tidak berpengaruh karena instans sedang diblokir.
Untuk mencegah instans diblokir untuk periode waktu yang lama, kami menyarankan Anda memperkirakan jumlah waktu yang diperlukan untuk mengeksekusi skrip Lua saat menulis skrip Lua, memeriksa loop tak terbatas, dan membagi skrip Lua jika perlu.
Jika skrip Lua sudah menjalankan perintah write terhadap dataset, perintah SCRIPT KILL tidak berpengaruh. Contoh kesalahan:
(error) UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.Solusi:
Pada halaman Instance List di konsol, temukan instans yang ingin Anda kelola dan klik Restart.
Masalah persistensi dan replikasi
Tair (Redis OSS-compatible) terus menyimpan skrip Lua yang telah dieksekusi di instans jika instans tidak di-restart atau perintah SCRIPT FLUSH tidak dijalankan untuk instans. Namun, Tair (Redis OSS-compatible) tidak dapat menjamin persistensi skrip Lua atau sinkronisasi skrip Lua dari node saat ini ke node lain dalam skenario seperti migrasi instans, perubahan konfigurasi, peningkatan versi, dan switchover instans.
Solusi:
Simpan semua skrip Lua di perangkat lokal Anda. Recache skrip Lua di Tair (Redis OSS-compatible) menggunakan perintah EVAL atau SCRIPT LOAD jika perlu. Ini mencegah kesalahan NOSCRIPT terjadi saat skrip Lua dihapus selama restart instans atau switchover ketersediaan tinggi (HA).
Batasan khusus pada instans kluster
Batasan arsitektur kluster
Untuk memastikan atomicitas eksekusi, skrip Lua tidak dapat dibagi dan hanya dapat dieksekusi pada satu shard dalam instans kluster. Dalam kebanyakan kasus, satu kunci digunakan untuk menentukan shard mana skrip Lua dirutekan. Oleh karena itu, Anda harus menentukan setidaknya satu kunci saat menjalankan skrip Lua dalam instans kluster. Jika Anda ingin membaca atau menulis beberapa kunci, kunci dalam satu skrip Lua harus milik slot yang sama. Jika tidak, hasil eksekusi abnormal akan dikembalikan. Skrip Lua tanpa kunci (seperti KEYS, SCAN, dan FLUSHDB) dapat dieksekusi secara normal, tetapi hanya data dari satu shard yang dikembalikan. Batasan ini disebabkan oleh arsitektur instans kluster.
Saat menjalankan perintah SCRIPT LOAD pada satu node, skrip Lua mungkin tidak disimpan pada node lain.
Kode kesalahan dalam mode proxy
Proxy melakukan pemeriksaan sintaksis untuk mengidentifikasi kunci yang termasuk dalam beberapa slot dan melemparkan pengecualian terlebih dahulu guna membantu pemecahan masalah. Proxy menggunakan metode pemeriksaan berbeda dari mesin virtual Lua. Hal ini menambah batasan tambahan pada eksekusi skrip Lua dalam mode proxy. Misalnya, perintah UNPACK tidak didukung, serta perintah EVAL, EVALSHA, dan SCRIPT tidak didukung dalam transaksi MULTI dan EXEC.
Anda juga dapat mengatur parameter script_check_enable untuk menonaktifkan pemeriksaan tambahan pada sintaksis Lua dalam mode proxy.
Jika parameter readonly_lua_route_ronode_enable diatur ke 1 untuk instans pemisahan baca/tulis, node proxy memeriksa apakah skrip Lua hanya berisi perintah read-only dan menentukan apakah akan meneruskannya ke node read-only. Logika pemeriksaan ini memberikan batasan pada sintaksis Lua.
Bagaimana pengaruh pengaturan parameter script_check_enable ke 0 terhadap instans?
Jika instans kompatibel dengan Redis 5.0 (dengan versi minor lebih awal dari 5.0.8) dan 4.0 atau lebih awal, kami menyarankan Anda tidak menonaktifkan pemeriksaan. Jika tidak, hasil normal mungkin dikembalikan saat skrip tidak dieksekusi secara normal.
Jika Anda menonaktifkan pemeriksaan pada versi lain, proxy tidak lagi memeriksa sintaksis Lua, tetapi node data masih memeriksa sintaksis Lua.
Berikut ini menjelaskan kode kesalahan dan penyebabnya.
Batasan arsitektur Redis Cluster
Kode kesalahan:
-ERR for redis cluster, eval/evalsha number of keys can't be negative or zero\r\nDeskripsi: Anda harus menyertakan kunci saat mengeksekusi skrip Lua. Node proxy menggunakan kunci untuk menentukan shard ke mana skrip Lua diteruskan.
# Contoh penggunaan valid EVAL "return redis.call('get', KEYS[1])" 1 fooeval # Contoh penggunaan tidak valid EVAL "return redis.call('get', 'foo')" 0Kode kesalahan:
-ERR kunci command 'xxx' harus berada di slot yang samaDeskripsi: Beberapa kunci dalam skrip Lua harus milik slot yang sama.
# Contoh penggunaan valid: EVAL "return redis.call('mget', KEYS[1], KEYS[2])" 2 foo {foo}bar # Contoh penggunaan tidak valid: EVAL "return redis.call('mget', KEYS[1], KEYS[2])" 2 foo foobar
Batasan tambahan yang disebabkan oleh pemeriksaan sintaksis Lua dalam mode proxy
Anda dapat menonaktifkan parameter script_check_enable untuk mencegah proxy melakukan pemeriksaan tambahan pada sintaksis Lua.
Kode kesalahan:
-ERR bad lua script for redis cluster, nested redis.call/redis.pcallDeskripsi: Panggilan bersarang tidak didukung. Anda dapat memanggil skrip Lua menggunakan variabel lokal.
# Contoh penggunaan valid EVAL "local value = redis.call('GET', KEYS[1]); redis.call('SET', KEYS[2], value)" 2 foo bar # Contoh penggunaan tidak valid EVAL "redis.call('SET', KEYS[1], redis.call('GET', KEYS[2]))" 2 foo barKode kesalahan:
-ERR bad lua script for redis cluster, first parameter of redis.call/pcall must be a single literal stringDeskripsi: Perintah yang dipanggil dalam redis.call/pcall harus berupa string literal.
# Contoh penggunaan valid eval "redis.call('GET', KEYS[1])" 1 foo # Contoh penggunaan tidak valid eval "local cmd = 'GET'; redis.call(cmd, KEYS[1])" 1 foo
Masalah izin baca/tulis
Kode kesalahan:
-ERR Write commands are not allowed from read-only scriptsDeskripsi: Skrip Lua yang dikirim menggunakan perintah EVAL_RO tidak boleh berisi perintah write.
Kode kesalahan:
-ERR bad write command in no write privilegeDeskripsi: Skrip Lua yang dikirim oleh akun read-only tidak boleh berisi perintah write.
Perintah yang tidak didukung
Kode kesalahan:
-ERR script debug not supportDeskripsi: Perintah SCRIPT DEBUG tidak didukung dalam mode proxy.
Kode kesalahan:
-ERR bad lua script for redis cluster, redis.call/pcall unkown redis command xxxDeskripsi: Skrip Lua berisi perintah yang tidak didukung dalam mode proxy. Untuk informasi lebih lanjut, lihat Batasan perintah untuk arsitektur kluster dan instans pemisahan baca/tulis.
Kesalahan sintaksis Lua
Kode kesalahan:
-ERR bad lua script for redis cluster, redis.call/pcall expect '('atau-ERR bad lua script for redis cluster, redis.call/redis.pcall definition is not complete, expect ')'Deskripsi: Kesalahan sintaksis Lua. Fungsi
redis.callharus diikuti oleh set lengkap(dan).Kode kesalahan:
-ERR bad lua script for redis cluster, at least 1 input key is needed for ZUNIONSTORE/ZINTERSTOREDeskripsi: Parameter numkeys dari perintah ZUNIONSTORE dan ZINTERSTORE harus lebih besar dari 0.
Kode kesalahan:
-ERR bad lua script for redis cluster, ZUNIONSTORE/ZINTERSTORE key count < numkeysDeskripsi: Jumlah kunci dalam perintah ZUNIONSTORE dan ZINTERSTORE kurang dari nilai numkeys.
Kode kesalahan:
-ERR bad lua script for redis cluster, xread/xreadgroup command syntax errorDeskripsi: Perintah XREAD atau XREADGROUP menggunakan sintaksis yang salah. Periksa jumlah parameter.
Kode kesalahan:
-ERR bad lua script for redis cluster, xread/xreadgroup command syntax error, streams must be specifiedDeskripsi: Parameter streams diperlukan dalam perintah XREAD dan XREADGROUP.
Kode kesalahan:
-ERR bad lua script for redis cluster, sort command syntax errorDeskripsi: Perintah SORT menggunakan sintaksis yang salah.
FAQ
Pertanyaan: Apakah DMS mendukung eksekusi skrip Lua?
Jawaban: Saat ini, perintah terkait skrip Lua tidak didukung di konsol DMS. Anda dapat menggunakan klien atau redis-cli untuk terhubung ke instans dan menjalankan skrip Lua.