redis初級之Lua指令碼

李秀秀xx發表於2024-11-28

Lua指令碼

1.簡介與用法

簡介

​ Lua語言是在1993年由巴西一個大學研究小組發明,其設計目標是作為嵌入式程式移植到其他應用程式,它是由C語言實現的,雖然簡單小巧但是功能強大,所以許多應用都選用它作為指令碼語言,尤其是在遊戲領域,例如大名鼎鼎的暴雪公司將Lua語言引入到“魔獸世界”這款遊戲中,Rovio公司將Lua語言作為“憤怒的小鳥”這款火爆遊戲的關卡升級引擎,Web伺服器Nginx將Lua語言作為擴充套件,增強自身功能。Redis將Lua作為指令碼語言可幫助開發者定製自己的Redis命令,在這之前,必須修改原始碼。

用法

資料結構及其處理邏輯

Lua語言提供瞭如下幾種資料型別:booleans(布林)、numbers(數值)、strings(字串)、tables(表格),和許多高階語言相比相對簡單。下面將結合例子對Lua的基本資料型別和邏輯處理進行說明。

(1).字串
-- 程式碼示例
local strings val = "hello"
print(val)
--執行結果:hello

print("world")
--執行結果:world

其中local代表val變數是一個區域性變數,如果沒有local修飾則代表是一個全域性變數。

(2).陣列

​ 在Lua中,如果要使用類似於陣列的功能,可以使用tables型別,下面程式碼使用定義了一個tables型別的變數myArrays,但和大多數程式語言不同的是,在Lua中的陣列下表從1開始計算。

-- 程式碼示例
local tables myArrays = {"hello","world",true,88.0}
print(myArrays[1])
print(myArrays[2])
print(myArrays[3])
print(myArrays[4])

--執行結果
-- hello
-- world
-- true
-- 88.0

如果想遍歷資料可以使用for或者while

for

下面程式碼會計算1到100的和,關鍵字for以end作為結束符。

-- 程式碼示例
local int sum = 0
for i = 1, 100
do
    sum = sum + i
end
print(sum)

--執行結果 5050

如果要遍歷陣列,則首先需要知道陣列的長度,只需要在變數前加一個#號即可

-- 程式碼示例
local tables myArrays = {"hello","world",true,88.0}
for i=1,#myArrays
do
    print(myArrays[i])
end

--執行結果
-- hello
-- world
-- true
-- 88.0

除此之外Lua還提供了內建函式ipairs,使用 for index, value in ipairs(tables)可以遍歷出所有的索引下標和值。

-- 程式碼示例
local tables myArrays = {"hello","world",true,88.0}
for index, value in ipairs(myArrays)
do
    print(index)
    print(value)
end

--執行結果
-- 1
-- hello
-- 2
-- world
-- 3
-- true
-- 4
-- 88.0
while

下面這段程式碼同樣會計算1到100的和。只不過使用的是while迴圈,while迴圈同樣以end為結束標記。

-- 程式碼示例
local int sum = 0
local int i = 1
while i <= 100
do
    sum = sum + i
    i = i + 1
end
print(sum)

--執行結果 5050
if else

要確定陣列中存不存在world,有則列印true,沒有則列印false,我們可以使用if else語句

-- 程式碼示例
local tables myArrays = {"hello","world",true,88.0}
local booleans ex = false
for index,value in ipairs(myArrays)
do
    if value == "world"
    then
        ex = true
        break
    end
end
if ex == true
then 
    print("true")
else
    print("false")
end

-- 執行結果 true
(3).雜湊

如果要使用類似於雜湊的功能,同樣可以使用tables型別,例如下面程式碼定義了一個tables,每個元素包含了key和value,其中strings1..strings2是將兩個字串進行連線。

-- 程式碼示例
local tables tom = {name = "tom",age = 18,height = 172.5}
print("姓名 : "..tom["name"]..",年齡 : "..tom["age"]..",身高 : "..tom["height"])

--執行結果
-- 姓名 : tom,年齡 : 18,身高 : 172.5

如果要遍歷雜湊,可以使用Lua的內建函式pairs

-- 程式碼示例
local tables tom = {name = "tom",age = 18,height = 172.5}
for key,value in pairs(tom)
do
    print(key .. ":" .. value)
end

--執行結果
-- height:172.5
-- age:18
-- name:tom

函式定義

​ 在Lua中,函式以function開頭,以end結尾,funcName是函式名,中間部分是函式體。

-- function funcName()
--     ···
-- end

-- 例如寫一個contact方法用來拼接兩個字串
function contact (str1 , str2)
    return str1 .." ".. str2
end
print(contact("hello ","world"))

--執行結果 hello world

2.Redis與Lua

在Redis中使用Lua

在Redis中執行Lua指令碼有兩種方法:eval 和 evalsha

(1) eval

eval 指令碼內容 key個數 key列表 引數列表

下面例子使用了key列表和引數列表來為Lua指令碼提供更多的靈活性:

> eval 'return "hello ".. KEYS[1] .. " " .. ARGV[1]' 1 redis world
"hello redis world"

此時KEYS[1] = redis,ARGV[1] = world,所以最終的列印結果為 hello redis world

注意:KEYS 和 ARGV 一定要大寫否則會報錯,如下

> eval 'return "hello ".. keys[1] .. " " .. argv[1]' 1 redis world
"ERR Error running script (call to f_9f158bb8946915295cd6c488611b5004b5bf66c9): @user_script:1: user_script:1: Script attempted to access nonexistent global variable 'keys'"

> eval 'return "hello ".. KEYS[1] .. " " .. argv[1]' 1 redis world
"ERR Error running script (call to f_6248f0862bc574fb60782a2d632d7db773e16286): @user_script:1: user_script:1: Script attempted to access nonexistent global variable 'argv'"

如果不需要傳KEYS或者ARGVS可以進行以下操作:

# 不傳KEYS
> eval 'return "hello ".. ARGV[1] .. " " .. ARGV[2]' 0 redis world
"hello redis world"

# 不傳ARGVS
> eval 'return "hello ".. KEYS[1] .. " " .. KEYS[2]' 2 redis world
"hello redis world"

如果Lua指令碼過長,還可以直接使用redis-cli --eval 直接執行Lua指令碼檔案。

eval命令和--eval引數本質是一樣的,客戶端如果想執行Lua指令碼,首先在客戶端編寫好Lua指令碼程式碼,然後把指令碼作為字串傳送給伺服器,伺服器端會將執行結果返回給客戶端。

注意:這裡的引數是需要使用逗號隔開的,逗號左右兩邊都需要有一個空格,沒有引數則不填。

[root@xxx src]# cat /root/myredis/script/contactStr.lua 
return ARGV[1].." "..ARGV[2]
[root@xxx src]# redis-cli -a xxx --eval /root/myredis/script/contactStr.lua , hello world
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
"hello world"

整個過程如圖所示

(2) evalsha

​ 除了使用eval,Redis還提供了evalsha命令來執行Lua指令碼。首先要將Lua指令碼載入到Redis服務端,得到該指令碼的SHA1校驗和,evalsha命令使用SHA1作為引數可以直接執行對應的Lua指令碼,避免每次傳送Lua指令碼的開銷。這樣客戶端就不需要每次執行指令碼內容,而指令碼也會常駐在服務端,指令碼功能得到了服用。

載入指令碼:

​ script load命令可以將指令碼內容載入到Redis記憶體中,例如下面將contactStr.lua指令碼載入到Redis中,得到SHA1為:9295ec8b57e8e4d0a3e89f6cb3ab5b225d2bcb34

# 程式碼示例
[root@xxx src]# redis-cli -a xxx script load "$(cat /root/myredis/script/contactStr.lua)"
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
"9295ec8b57e8e4d0a3e89f6cb3ab5b225d2bcb34"
執行指令碼:

​ evalsha的使用方法如下,引數使用SHA1值,執行邏輯和eval一致。

evalsha 指令碼SHA1值 key個數 key列表 引數列表

# 程式碼示例
[root@xxx src]# redis-cli -a xxx evalsha 9295ec8b57e8e4d0a3e89f6cb3ab5b225d2bcb34 0 hello world
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
"hello world"

Lua的Redis API

​ Lua可以使用redis.call函式實現對Redis的訪問,例如下面的程式碼是Lua使用redis.call呼叫了Redis的set和get操作:

-- 程式碼示例
redis.call("set","hello","world")
redis.call("get","hello")

​ 放在Redis的執行效果如下:

# 程式碼示例
> set hello world
"OK"

> eval 'return redis.call("get",KEYS[1])' 1 hello
"world"

​ 除此之外Lua還可以使用redis.pcall函式實現對Redis的呼叫,redis.call和redis.pcall的不同在於,如果redis.call執行失敗,那麼指令碼執行結束會直接返回錯誤,而redis.pcall會忽略錯誤繼續執行指令碼。

3.Redis如何管理Lua

​ Redis提供了4個命令實現對Lua指令碼的管理。

(1) script load

​ 此命令用於將Lua指令碼載入到Redis記憶體中

# 程式碼示例
[root@xxx src]# redis-cli -a xxx script load "$(cat /root/myredis/script/contactStr.lua)"
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
"9295ec8b57e8e4d0a3e89f6cb3ab5b225d2bcb34"

(2) script exists

​ 此命令用於判斷sha1是否已經載入到Redis記憶體中

# 程式碼示例
> script exists 9295ec8b57e8e4d0a3e89f6cb3ab5b225d2bcb34
1) "1"

因為這裡的引數可以填多個,返回值為被載入到Redis記憶體中的Lua指令碼個數。

(3) script flush

​ 此命令用於清除Redis記憶體已經載入的所有Lua指令碼。

# 程式碼示例
> script flush
"OK"

> script exists 9295ec8b57e8e4d0a3e89f6cb3ab5b225d2bcb34
1) "0"

(4) script kill

此命令用於殺掉正在執行的Lua指令碼。如果Lua指令碼比較耗時,甚至Lua指令碼存在問題,那麼此時Lua指令碼的執行會阻塞Redis,直到指令碼執行完畢或者外部進行干預將其結束。

相關文章