Redis中Lua腳本的使用和設(shè)置超時(shí)
Redis提供了Lua腳本功能來(lái)讓用戶(hù)實(shí)現(xiàn)自己的原子命令,但也存在著風(fēng)險(xiǎn),編寫(xiě)不當(dāng)?shù)哪_本可能阻塞線(xiàn)程導(dǎo)致整個(gè)Redis服務(wù)不可用。
本文將介紹Redis中Lua腳本的基本用法,以及腳本超時(shí)導(dǎo)致的問(wèn)題和處理方式。
EVAL命令簡(jiǎn)介
eval格式
Redis 提供了命令EVAL
來(lái)執(zhí)行Lua腳本,格式如下
EVAL script numkeys key [key …] arg [arg …]
其中 script
是將要執(zhí)行的腳本內(nèi)容,至于后面的腳本參數(shù)部分與本文無(wú)關(guān),在此不做贅述。
特性
由于Redis對(duì)數(shù)據(jù)集單線(xiàn)程讀寫(xiě)的特性,Lua腳本執(zhí)行時(shí)會(huì)阻塞所有對(duì)數(shù)據(jù)集的讀寫(xiě)操作,這給它帶來(lái)了下面兩個(gè)特性:
- 原子性:可以通過(guò)Lua腳本實(shí)現(xiàn)對(duì)數(shù)據(jù)集的原子讀寫(xiě)操作,這和Redis的事務(wù)功能
MULTI / EXEC
類(lèi)似 - 長(zhǎng)時(shí)間阻塞風(fēng)險(xiǎn):如果Lua腳本執(zhí)行時(shí)間過(guò)長(zhǎng),導(dǎo)致整個(gè)Redis不可用
執(zhí)行流程
已 eval "return 'hello world'" 0
為例,腳本執(zhí)行步驟如下
定義腳本函數(shù)
執(zhí)行過(guò)的腳本可以根據(jù)hash值找到函數(shù)重新使用
Redis會(huì)根據(jù)傳入的腳本內(nèi)容生成函數(shù),函數(shù)名由 f_
+ 腳本內(nèi)容的sha1摘要組成。
function f_5332031c6b470dc5a0dd9b4bf2030dea6d65de91() return 'hello world' end
函數(shù)保存到 Lua_scripts
字典,便于 evalsha
使用
執(zhí)行腳本函數(shù)
- 將KEYS和ARGV兩個(gè)參數(shù)數(shù)組傳入Lua執(zhí)行環(huán)境
- 裝載超時(shí)處理鉤子
- 執(zhí)行腳本
- 移除超時(shí)鉤子
- 結(jié)果保存到客戶(hù)端輸出緩沖區(qū),等待服務(wù)器將結(jié)果返回客戶(hù)端
- Lua環(huán)境垃圾回收
關(guān)于腳本超時(shí)
介紹完EVAL命令,下面來(lái)關(guān)注Lua腳本長(zhǎng)時(shí)間阻塞的風(fēng)險(xiǎn)。
Redis的配置文件中提供了如下配置項(xiàng)來(lái)規(guī)定最大執(zhí)行時(shí)長(zhǎng)
Lua-time-limit 5000
Lua腳本最大執(zhí)行時(shí)間,默認(rèn)5秒
但這里有個(gè)坑,當(dāng)一個(gè)腳本達(dá)到最大執(zhí)行時(shí)長(zhǎng)的時(shí)候,Redis并不會(huì)強(qiáng)制停止腳本的運(yùn)行,僅僅在日志里打印個(gè)警告,告知有腳本超時(shí)。
Lua slow script detected: still in execution after 5000 milliseconds. You can try killing the script using the SCRIPT KILL command. Script SHA1 is: 2531e4edc1a1e2a9bac3c52e99466f9ccabf12c0
為什么不能直接停掉呢?
因?yàn)?Redis 必須保證腳本執(zhí)行的原子性,中途停止可能導(dǎo)致內(nèi)存的數(shù)據(jù)集上只修改了部分?jǐn)?shù)據(jù)。
(只讀的腳本應(yīng)該是可以自動(dòng)停的,沒(méi)自動(dòng)停的原因我猜測(cè)是:腳本超時(shí)嚴(yán)重可以肯定出現(xiàn)了編碼錯(cuò)誤,作者可能希望在測(cè)試中盡早發(fā)現(xiàn)這種問(wèn)題,而不是靠自動(dòng)停止導(dǎo)致bug被忽略?)
如果時(shí)長(zhǎng)達(dá)到 Lua-time-limit
規(guī)定的最大執(zhí)行時(shí)間,Redis只會(huì)做這幾件事情:
日志記錄有腳本運(yùn)行超時(shí)
開(kāi)始允許接受其他客戶(hù)端請(qǐng)求,但僅限于 SCRIPT KILL
和 SHUTDOWN NOSAVE
兩個(gè)命令
其他請(qǐng)求仍返回busy錯(cuò)誤
SCRIPT KILL 命令
如果Lua只是讀取數(shù)據(jù)而沒(méi)做修改的話(huà),執(zhí)行 SCRIPT KILL
就可以直接終止腳本執(zhí)行,不用擔(dān)心數(shù)據(jù)被修改。
但是,如果腳本已經(jīng)改寫(xiě)了數(shù)據(jù)內(nèi)容,SCRIPT KILL
將報(bào)出以下錯(cuò)誤,因?yàn)樗茐臄?shù)據(jù)集的內(nèi)容。
(error) UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.
SHUTDOWN NOSAVE 命令
如上所述,如果腳本已經(jīng)執(zhí)行了寫(xiě)命令,SCRIPT KILL
將無(wú)法執(zhí)行。那我們就只剩以下兩種選擇了:
- 繼續(xù)等待腳本執(zhí)行完成
- 使用
SHUTDOWN NOSAVE
來(lái)直接停掉 Redis,并避免臟數(shù)據(jù)持久化到磁盤(pán)
最后,不知道你有沒(méi)有疑問(wèn),從開(kāi)始執(zhí)行腳本到 SHUTDOWN 之間的寫(xiě)命令會(huì)把日志寫(xiě)到AOF里嗎?Lua腳本中的命令什么時(shí)候會(huì)寫(xiě)AOF里?
講道理,既然 Redis 為了不破壞腳本的原子性而不讓SCRIPT KILL
執(zhí)行,那么腳本中寫(xiě)命令的 “提交” 也應(yīng)當(dāng)是原子執(zhí)行的,而不是執(zhí)行一句就向AOF里寫(xiě)一句。
“提交”:借用數(shù)據(jù)庫(kù)中 commit 的概念,這里指寫(xiě)入AOF文件中
下面就來(lái)驗(yàn)證這個(gè)猜測(cè):
先執(zhí)行 tail -f appendonly.aof
實(shí)時(shí)查看AOF文件變化
再開(kāi)一個(gè)redis-cli 命令行執(zhí)行一個(gè)內(nèi)容如下的Lua腳本
redis.call('set','a','aaaa') --先執(zhí)行寫(xiě)命令 local count = 1 while( 999999999 > count ) -- 阻塞幾秒 do count = count+1 end
127.0.0.1:6379> eval "redis.call('set','a','aaaa') local count = 1 while( 999999999 > count ) do count = count+1 end" 0 (nil) (8.65s)
現(xiàn)象是,腳本剛開(kāi)始執(zhí)行,AOF文件毫無(wú)反應(yīng),一直等到8秒后腳本完成,命令才追加寫(xiě)入到AOF中。
這就驗(yàn)證了Redis腳本里的寫(xiě)命令是等到執(zhí)行完成后再一次性寫(xiě)入AOF的。
參考
Redis設(shè)計(jì)與實(shí)現(xiàn)
到此這篇關(guān)于Redis中Lua腳本的使用和設(shè)置超時(shí) 的文章就介紹到這了,更多相關(guān)Redis Lua 超時(shí)內(nèi)容請(qǐng)搜索本站以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持本站!
版權(quán)聲明:本站文章來(lái)源標(biāo)注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請(qǐng)保持原文完整并注明來(lái)源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非www.sddonglingsh.com所屬的服務(wù)器上建立鏡像,否則將依法追究法律責(zé)任。本站部分內(nèi)容來(lái)源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來(lái),僅供學(xué)習(xí)參考,不代表本站立場(chǎng),如有內(nèi)容涉嫌侵權(quán),請(qǐng)聯(lián)系alex-e#qq.com處理。