參考:
- http://www.redis.cn/commands/eval.html
- https://www.runoob.com/redis/redis-scripting.html
Redis從2.6.0版本開始支援Lua腳本,通過在伺服器嵌入Lua環境,Redis用戶端可以使用Lua腳本,直接在伺服器端原子地執行多個Redis指令。
1.使用Lua腳本的好處:
- 減少網絡開銷:可以将多個請求通過腳本的形式一次發送,減少網絡時延。
- 原子操作:redis會将整個腳本作為一個整體執行,中間不會被其他指令插入。是以在編寫腳本的過程中無需擔心會出現競态條件,無需使用事務。
- 複用:用戶端發送的腳本會永久存在redis中,這樣,其他用戶端可以複用這一腳本而不需要使用代碼完成相同的邏輯。
2.相關指令:
- EVAL:執行 Lua 腳本
- EVALSHA:根據給定的 sha1 校驗碼,執行 Lua 腳本
- SCRIPT DEBUG:使用EVAL可以開啟對腳本的調試(3.2.0版本)
- SCRIPT EXISTS:檢視指定的腳本是否已經被儲存在緩存當中
- SCRIPT FLUSH:從腳本緩存中移除所有腳本
- SCRIPT KILL:殺死目前正在運作的 Lua 腳本
- SCRIPT LOAD:将腳本 script 添加到腳本緩存中,但并不立即執行這個腳本
一、EVAL
EVAL script numkeys key [key ...] arg [arg ...]
- script:參數是一段 Lua 5.1 腳本程式。腳本不必(也不應該)定義為一個 Lua 函數
- numbers:指定鍵名參數的個數
- key:表示在腳本中用到的Redis 鍵(key),這些鍵名參數可以在Lua中通過全局變量
數組通路,下标從1開始,如: KEYS[1]、KEYS[2]KEYS
- arg:參數,在Lua中通過全局變量
數組通路,下标從1開始,如:ARGV[1]、ARGV[2]ARGV
示例:
(1)使用
KEYS
數組、
ARGV
數組
redis> eval "return {KEYS[1], KEYS[2], ARGV[1], ARGV[2]}" 2 key1 key2 arg1 arg2
1) "key1"
2) "key2"
3) "arg1"
4) "arg2"
(2)輸出list中所有元素
redis> rpush nums 1 2 3
(integer) 3
redis> eval "return redis.call('lrange', KEYS[1], 0, -1)" 1 nums
1) "1"
2) "2"
3) "3"
(3)集合去重
使用lua腳本檔案方式,在指令行裡執行,和在redis裡執行不太一樣,指令如下:
redis-cli --eval lua_file key1 key2 , arg1 arg2 arg3
- 參考:https://www.cnblogs.com/tinywan/p/9643022.html
示例:
- 插入測試資料
redis> sadd nums 1 2 3
(integer) 3
redis> smembers nums
1) "1"
2) "2"
3) "3"
- member.lua腳本
-- key
local key = KEYS[1]
-- 所有參數
local args = ARGV
-- 初始化result
local result = {}
-- 判斷元素是否存在
for m, n in ipairs(args) do
local is_repeat = redis.call("sismember", key, n)
if (is_repeat) then
table.insert(result, 1, n)
end
end
return result
- 測試
$ redis-cli --eval ./member.lua nums , 1 2
1) "2"
2) "1"
二、SCRIPT LOAD、EVALSHA
-
:将腳本 script 添加到腳本緩存中,但并不立即執行這個腳本SCRIPT LOAD script
- script:lua腳本
-
:根據給定的 sha1 校驗碼,執行Lua腳本EVALSHA sha1 numkeys key [key ...] arg [arg ...]
- sha1: 通過 SCRIPT LOAD 生成的 sha1 校驗碼
- 其他參數同
指令EVAL
示例:
redis> script load "return 'hello redis'"
"69dd69fc0ba1e25d8e2972008b6baee8eccf7da6"
redis> evalsha 69dd69fc0ba1e25d8e2972008b6baee8eccf7da6 0
"hello redis"
三、SCRIPT EXISTS、SCRIPT FLUSH
-
:檢視指定的腳本是否已經被儲存在緩存當中SCRIPT EXISTS script [script ...]
- script:通過 SCRIPT LOAD 生成的 sha1 校驗碼
-
:從腳本緩存中移除所有腳本SCRIPT FLUSH
示例:
redis> script load "return 'hello redis'"
"69dd69fc0ba1e25d8e2972008b6baee8eccf7da6"
redis> script exists 69dd69fc0ba1e25d8e2972008b6baee8eccf7da6
1) (integer) 1
redis> script flush
OK
redis> script exists 69dd69fc0ba1e25d8e2972008b6baee8eccf7da6
1) (integer) 0
四、SCRIPT KILL
SCRIPT KILL
:殺死目前正在運作的 Lua 腳本,當且僅當這個腳本沒有執行過任何寫操作時,這個指令才生效
注:
- 這個指令主要用于終止運作時間過長的腳本,比如一個因為 BUG 而發生無限循環的腳本。
-
執行之後,目前正在運作的腳本會被殺死,執行這個腳本的用戶端會從 EVAL 指令的阻塞當中退出,并收到一個錯誤作為傳回值。SCRIPT KILL
示例:
用戶端A:
# 目前沒有正在執行的腳本
redisA> script kill
(error) NOTBUSY No scripts in execution right now.
# 用戶端B執行腳本,用戶端A殺死運作的腳本
redis> script kill
OK
(0.92s)
用戶端B:
# 腳本死循環
redisB> eval "local a = 1; while 1 do a=1; end" 0
# 被殺死後,傳回以下資訊
(error) ERR Error running script (call to f_c8600a7388e4dd63490c59ede33602dbef5971eb): @user_script:1: Script killed by user with SCRIPT KILL...
(5.00s)
五、SCRIPT DEBUG
SCRIPT DEBUG YES|SYNC|NO
:使用EVAL可以開啟對腳本的調試
- YES:打開非阻塞異步調試模式,調試Lua腳本(回退修改的資料)
- SYNC:打開阻塞同步調試模式,調試Lua腳本(保留修改的資料)
- NO:關閉腳本調試模式
注:LDB可以設定成兩種模式:同步和異步。
- 異步模式下,伺服器會建立新的調試連接配接,不阻塞其他連接配接,同時在調試連接配接結束後會復原所有的資料修改,這可以保證再次調試時初始狀态不變。
- 同步模式下,調試過程中,伺服器其他連接配接會被阻塞,當調試結束後,所有的資料修改會被儲存。
示例:
(1)redis内調試
# 異步調試,回退修改後的資料
redis> script debug yes
OK
# 測試腳本
redis> eval "local a = 1; \n local b = 2; \n return 3" 0
* Stopped at 1, stop reason = step over
-> 1 local a = 1;
# 使用 n 單步調試
redis> n
* Stopped at 2, stop reason = step over
-> 2 local b = 2;
redis> n
* Stopped at 3, stop reason = step over
-> 3 return 3
redis> n
(integer) 3
(Lua debugging session ended -- dataset changes rolled back)
(2)指令行内調試
使用第一節中的示例(3),指令中添加
--ldb
進入調試模式:
redis-cli --ldb --eval ./member.lua nums , 1 2
- 進入調試模式後,通過 n 或 s 進行單步調試,直到結束
- 預設是異步模式,調試結束後復原修改的資料
- 使用
進入同步模式,此時伺服器其他連結被阻塞,調試結束後修改後的資料會被儲存--ldb-sync-mode
調試示例:【參考:http://blog.huangz.me/2017/redis-lua-debuger-introduction.html】
$ redis-cli --ldb --eval ./member.lua nums , 1 2
Lua debugging session started, please use:
quit -- End the session.
restart -- Restart the script in debug mode again.
help -- Show Lua script debugging commands.
* Stopped at 2, stop reason = step over
-> 2 local key = KEYS[1]
lua debugger> n
* Stopped at 5, stop reason = step over
-> 5 local args = ARGV
lua debugger> n
* Stopped at 8, stop reason = step over
-> 8 local result = {}
lua debugger> n
* Stopped at 11, stop reason = step over
-> 11 for m, n in ipairs(args) do
lua debugger> n
* Stopped at 12, stop reason = step over
-> 12 local is_repeat = redis.call("sismember", key, n)
lua debugger> n
<redis> sismember nums 1
<reply> 1
* Stopped at 13, stop reason = step over
-> 13 if (is_repeat) then
lua debugger> n
* Stopped at 14, stop reason = step over
-> 14 table.insert(result, 1, n)
lua debugger> n
* Stopped at 11, stop reason = step over
-> 11 for m, n in ipairs(args) do
lua debugger> n
* Stopped at 12, stop reason = step over
-> 12 local is_repeat = redis.call("sismember", key, n)
lua debugger> n
<redis> sismember nums 2
<reply> 1
* Stopped at 13, stop reason = step over
-> 13 if (is_repeat) then
lua debugger> n
* Stopped at 14, stop reason = step over
-> 14 table.insert(result, 1, n)
lua debugger> n
* Stopped at 11, stop reason = step over
-> 11 for m, n in ipairs(args) do
lua debugger> n
* Stopped at 17, stop reason = step over
-> 17 return result
lua debugger> n
1) "2"
2) "1"
(Lua debugging session ended -- dataset changes rolled back)