天天看點

Redis學習筆記 - Lua腳本(1) - 使用Lua腳本

參考:

  • 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中通過全局變量

    KEYS

    數組通路,下标從1開始,如: KEYS[1]、KEYS[2]
  • arg:參數,在Lua中通過全局變量

    ARGV

    數組通路,下标從1開始,如:ARGV[1]、ARGV[2]

示例:

(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 LOAD script

    :将腳本 script 添加到腳本緩存中,但并不立即執行這個腳本
    • script:lua腳本
  • EVALSHA sha1 numkeys key [key ...] arg [arg ...]

    :根據給定的 sha1 校驗碼,執行Lua腳本
    • 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 而發生無限循環的腳本。
  • SCRIPT KILL

    執行之後,目前正在運作的腳本會被殺死,執行這個腳本的用戶端會從 EVAL 指令的阻塞當中退出,并收到一個錯誤作為傳回值。

示例:

用戶端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)
           

繼續閱讀