Lua腳本
Lua是一個高效的輕量級腳本語言,用標(biāo)準(zhǔn)C語言編寫并以源代碼形式開放较鼓, 其設(shè)計目的是為了嵌入應(yīng)用程序中椎木,從而為應(yīng)用程序提供靈活的擴(kuò)展和定制功能。
使用腳本的好處
- 減少網(wǎng)絡(luò)開銷博烂,在Lua腳本中可以把多個命令放在同一個腳本中運(yùn)行香椎。
- 原子操作,Redis會將整個腳本作為一個整體執(zhí)行脖母,中間不會被其他命令插入士鸥。換句話說,編寫腳本的過程中無需擔(dān)心會出現(xiàn)競態(tài)條件谆级。
- 復(fù)用性烤礁,客戶端發(fā)送的腳本會永遠(yuǎn)存儲在Redis中,這意味著其他客戶端可以復(fù)用這一腳本來完成同樣的邏輯肥照。
Lua在Linux中的安裝
到官網(wǎng)下載lua的tar.gz的源碼包
tar -zxvf lua.tar.gz
進(jìn)入解壓的目錄:
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命令即可進(jìn)入lua的控制臺
Redis與Lua
在Lua腳本中調(diào)用Redis命令舆绎,可以使用redis.call函數(shù)調(diào)用鲤脏。比如我們調(diào)用string類型的命令。
redis.call(‘set’,’hello’,’world’)
redis.call 函數(shù)的返回值就是redis命令的執(zhí)行結(jié)果吕朵。redis.call函數(shù)會將這5種類型的返回值轉(zhuǎn)化對應(yīng)的Lua的數(shù)據(jù)類型猎醇。
- 從Lua腳本中獲得返回值
在很多情況下我們都需要腳本可以有返回值,在腳本中可以使用return 語句將值返回給redis客戶端努溃,通過return語句來執(zhí)行硫嘶,如果沒有執(zhí)行return,默認(rèn)返回為nil梧税。 - 如何在redis中執(zhí)行l(wèi)ua腳本
Redis提供了EVAL命令可以使開發(fā)者像調(diào)用其他Redis內(nèi)置命令一樣調(diào)用腳本沦疾。
[EVAL] [腳本內(nèi)容] [key參數(shù)的數(shù)量] [key …] [arg …]
可以通過key和arg這兩個參數(shù)向腳本中傳遞數(shù)據(jù),他們的值可以在腳本中分別使用KEYS和ARGV 這兩個類型的全局變量訪問第队。比如我們通過腳本實現(xiàn)一個set命令哮塞,通過在redis客戶端中調(diào)用,那么執(zhí)行的語句是:
lua腳本的內(nèi)容為: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兩個表類型的全局變量凳谦。當(dāng)腳本不需要任何參數(shù)時也不能省略這個參數(shù)忆畅。如果沒有參數(shù)則為0
eval "return redis.call(‘get’,’hello’)" 0
- EVALSHA命令
考慮到我們通過eval執(zhí)行l(wèi)ua腳本,腳本比較長的情況下尸执,每次調(diào)用腳本都需要把整個腳本傳給redis邻眷,比較占用帶寬眠屎。為了解決這個問題,redis提供了EVALSHA命令允許開發(fā)者通過腳本內(nèi)容的SHA1摘要來執(zhí)行腳本肆饶。該命令的用法和EVAL一樣,只不過是將腳本內(nèi)容替換成腳本內(nèi)容的SHA1摘要岖常。
- Redis在執(zhí)行EVAL命令時會計算腳本的SHA1摘要并記錄在腳本緩存中驯镊。
- 執(zhí)行EVALSHA命令時Redis會根據(jù)提供的摘要從腳本緩存中查找對應(yīng)的腳本內(nèi)容,如果找到了就執(zhí)行腳本竭鞍,否則返回“NOSCRIPT No matching script,Please use EVAL”板惑。
通過案例來演示EVALSHA命令的效果
script load "return redis.call('get','hello')"
將腳本加入緩存并生成sha1命令
evalsha "a5a402e90df3eaeca2ff03d56d99982e05cf6574" 0
調(diào)用eval命令之前,先執(zhí)行evalsha命令偎快,如果提示腳本不存在冯乘,則再調(diào)用eval命令。
Lua腳本實戰(zhàn)
需求:實現(xiàn)一個針對某個手機(jī)號的訪問頻次?
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í)行時間過程導(dǎo)致Redis無法提供服務(wù)丐怯。Redis提供了lua-time-limit參數(shù)限制腳本的最長運(yùn)行時間喷好。默認(rèn)是5秒鐘。
當(dāng)腳本運(yùn)行時間超過這個限制后读跷,Redis將開始接受其他命令但不會執(zhí)行(以確保腳本的原子性)梗搅,而是返回BUSY的錯誤。
實踐操作
打開兩個客戶端窗口
在第一個窗口中執(zhí)行l(wèi)ua腳本的死循環(huán) eval “while true do end” 0
在第二個窗口中運(yùn)行g(shù)et hello
第二個窗口的運(yùn)行結(jié)果是Busy, 可以通過script kill命令終止正在執(zhí)行的腳本效览。如果當(dāng)前執(zhí)行的lua腳本對redis的數(shù)據(jù)進(jìn)行了修改无切,比如(set)操作,那么script kill命令沒辦法終止腳本的運(yùn)行丐枉,因為要保證lua腳本的原子性哆键。如果執(zhí)行一部分終止了,就違背了這一個原則在這種情況下矛洞,只能通過 shutdown nosave命令強(qiáng)行終止洼哎。