Lua腳本
Lua是一個高效的輕量級腳本語言,用標準C語言編寫并以源代碼形式開放, 其設計目的是為了嵌入應用程序中,從而為應用程序提供靈活的擴展和定制功能。
使用腳本的好處
- 減少網(wǎng)絡開銷,在Lua腳本中可以把多個命令放在同一個腳本中運行。
- 原子操作,Redis會將整個腳本作為一個整體執(zhí)行,中間不會被其他命令插入。換句話說,編寫腳本的過程中無需擔心會出現(xiàn)競態(tài)條件。
- 復用性,客戶端發(fā)送的腳本會永遠存儲在Redis中,這意味著其他客戶端可以復用這一腳本來完成同樣的邏輯。
Lua在Linux中的安裝
到官網(wǎng)下載lua的tar.gz的源碼包
tar -zxvf lua.tar.gz
進入解壓的目錄:
cd lua
make linux (linux環(huán)境下編譯)
make install
如果報錯,說找不到readline/readline.h, 可以通過yum命令安裝
yum -y install readline-devel ncurses-devel
安裝完以后再make linux / make install
最后,直接輸入 lua命令即可進入lua的控制臺
Redis與Lua
在Lua腳本中調用Redis命令,可以使用redis.call函數(shù)調用。比如我們調用string類型的命令。
redis.call(‘set’,’hello’,’world’)
redis.call 函數(shù)的返回值就是redis命令的執(zhí)行結果。redis.call函數(shù)會將這5種類型的返回值轉化對應的Lua的數(shù)據(jù)類型。
- 從Lua腳本中獲得返回值
在很多情況下我們都需要腳本可以有返回值,在腳本中可以使用return 語句將值返回給redis客戶端,通過return語句來執(zhí)行,如果沒有執(zhí)行return,默認返回為nil。 - 如何在redis中執(zhí)行l(wèi)ua腳本
Redis提供了EVAL命令可以使開發(fā)者像調用其他Redis內置命令一樣調用腳本。
[EVAL] [腳本內容] [key參數(shù)的數(shù)量] [key …] [arg …]
可以通過key和arg這兩個參數(shù)向腳本中傳遞數(shù)據(jù),他們的值可以在腳本中分別使用KEYS和ARGV 這兩個類型的全局變量訪問。比如我們通過腳本實現(xiàn)一個set命令,通過在redis客戶端中調用,那么執(zhí)行的語句是:
lua腳本的內容為:return redis.call(‘set’,KEYS[1],ARGV[1])//KEYS和ARGV必須大寫
eval "return redis.call('set',KEYS[1],ARGV[1])" 1 hello world
EVAL命令是根據(jù) key參數(shù)的數(shù)量-也就是上面例子中的1來將后面所有參數(shù)分別存入腳本中KEYS和ARGV兩個表類型的全局變量。當腳本不需要任何參數(shù)時也不能省略這個參數(shù)。如果沒有參數(shù)則為0
eval "return redis.call(‘get’,’hello’)" 0 - EVALSHA命令
考慮到我們通過eval執(zhí)行l(wèi)ua腳本,腳本比較長的情況下,每次調用腳本都需要把整個腳本傳給redis,比較占用帶寬。為了解決這個問題,redis提供了EVALSHA命令允許開發(fā)者通過腳本內容的SHA1摘要來執(zhí)行腳本。該命令的用法和EVAL一樣,只不過是將腳本內容替換成腳本內容的SHA1摘要。
- Redis在執(zhí)行EVAL命令時會計算腳本的SHA1摘要并記錄在腳本緩存中。
- 執(zhí)行EVALSHA命令時Redis會根據(jù)提供的摘要從腳本緩存中查找對應的腳本內容,如果找到了就執(zhí)行腳本,否則返回“NOSCRIPT No matching script,Please use EVAL”。
通過案例來演示EVALSHA命令的效果
script load "return redis.call('get','hello')" 將腳本加入緩存并生成sha1命令
evalsha "a5a402e90df3eaeca2ff03d56d99982e05cf6574" 0
調用eval命令之前,先執(zhí)行evalsha命令,如果提示腳本不存在,則再調用eval命令。
Lua腳本實戰(zhàn)
需求:實現(xiàn)一個針對某個手機號的訪問頻次?
local num=redis.call('incr',KEYS[1])
if tonumber(num)==1 then
redis.call('expire',KEYS[1],ARGV[1])
return 1
elseif tonumber(num)>tonumber(ARGV[2]) then
return 0
else
return 1
end
執(zhí)行命令:./redis-cli --eval xxx.lua rate.limiting:13700000000 , 10 3
語法為: ./redis-cli –eval [lua腳本] [key…]空格,空格[args…]
腳本的原子性
Redis的腳本執(zhí)行是原子的,即腳本執(zhí)行期間Redis不會執(zhí)行其他命令。所有的命令必須等待腳本執(zhí)行完以后才能執(zhí)行。為了防止某個腳本執(zhí)行時間過程導致Redis無法提供服務。Redis提供了lua-time-limit參數(shù)限制腳本的最長運行時間。默認是5秒鐘。
當腳本運行時間超過這個限制后,Redis將開始接受其他命令但不會執(zhí)行(以確保腳本的原子性),而是返回BUSY的錯誤。
實踐操作
打開兩個客戶端窗口
在第一個窗口中執(zhí)行l(wèi)ua腳本的死循環(huán) eval “while true do end” 0
在第二個窗口中運行get hello
第二個窗口的運行結果是Busy, 可以通過script kill命令終止正在執(zhí)行的腳本。如果當前執(zhí)行的lua腳本對redis的數(shù)據(jù)進行了修改,比如(set)操作,那么script kill命令沒辦法終止腳本的運行,因為要保證lua腳本的原子性。如果執(zhí)行一部分終止了,就違背了這一個原則在這種情況下,只能通過 shutdown nosave命令強行終止。