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,直到指令碼執行完畢或者外部進行干預將其結束。