Redis中Lua腳本的使用和設(shè)置超時
Redis提供了Lua腳本功能來讓用戶實現(xiàn)自己的原子命令,但也存在著風(fēng)險,編寫不當(dāng)?shù)哪_本可能阻塞線程導(dǎo)致整個Redis服務(wù)不可用。
本文將介紹Redis中Lua腳本的基本用法,以及腳本超時導(dǎo)致的問題和處理方式。
EVAL命令簡介
eval格式
Redis 提供了命令EVAL
來執(zhí)行Lua腳本,格式如下
EVAL script numkeys key [key …] arg [arg …]
其中 script
是將要執(zhí)行的腳本內(nèi)容,至于后面的腳本參數(shù)部分與本文無關(guān),在此不做贅述。
特性
由于Redis對數(shù)據(jù)集單線程讀寫的特性,Lua腳本執(zhí)行時會阻塞所有對數(shù)據(jù)集的讀寫操作,這給它帶來了下面兩個特性:
- 原子性:可以通過Lua腳本實現(xiàn)對數(shù)據(jù)集的原子讀寫操作,這和Redis的事務(wù)功能
MULTI / EXEC
類似 - 長時間阻塞風(fēng)險:如果Lua腳本執(zhí)行時間過長,導(dǎo)致整個Redis不可用
執(zhí)行流程
已 eval "return 'hello world'" 0
為例,腳本執(zhí)行步驟如下
定義腳本函數(shù)
執(zhí)行過的腳本可以根據(jù)hash值找到函數(shù)重新使用
Redis會根據(jù)傳入的腳本內(nèi)容生成函數(shù),函數(shù)名由 f_
+ 腳本內(nèi)容的sha1摘要組成。
function f_5332031c6b470dc5a0dd9b4bf2030dea6d65de91() return 'hello world' end
函數(shù)保存到 Lua_scripts
字典,便于 evalsha
使用
執(zhí)行腳本函數(shù)
- 將KEYS和ARGV兩個參數(shù)數(shù)組傳入Lua執(zhí)行環(huán)境
- 裝載超時處理鉤子
- 執(zhí)行腳本
- 移除超時鉤子
- 結(jié)果保存到客戶端輸出緩沖區(qū),等待服務(wù)器將結(jié)果返回客戶端
- Lua環(huán)境垃圾回收
關(guān)于腳本超時
介紹完EVAL命令,下面來關(guān)注Lua腳本長時間阻塞的風(fēng)險。
Redis的配置文件中提供了如下配置項來規(guī)定最大執(zhí)行時長
Lua-time-limit 5000
Lua腳本最大執(zhí)行時間,默認(rèn)5秒
但這里有個坑,當(dāng)一個腳本達(dá)到最大執(zhí)行時長的時候,Redis并不會強(qiáng)制停止腳本的運行,僅僅在日志里打印個警告,告知有腳本超時。
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
為什么不能直接停掉呢?
因為 Redis 必須保證腳本執(zhí)行的原子性,中途停止可能導(dǎo)致內(nèi)存的數(shù)據(jù)集上只修改了部分?jǐn)?shù)據(jù)。
(只讀的腳本應(yīng)該是可以自動停的,沒自動停的原因我猜測是:腳本超時嚴(yán)重可以肯定出現(xiàn)了編碼錯誤,作者可能希望在測試中盡早發(fā)現(xiàn)這種問題,而不是靠自動停止導(dǎo)致bug被忽略?)
如果時長達(dá)到 Lua-time-limit
規(guī)定的最大執(zhí)行時間,Redis只會做這幾件事情:
日志記錄有腳本運行超時
開始允許接受其他客戶端請求,但僅限于 SCRIPT KILL
和 SHUTDOWN NOSAVE
兩個命令
其他請求仍返回busy錯誤
SCRIPT KILL 命令
如果Lua只是讀取數(shù)據(jù)而沒做修改的話,執(zhí)行 SCRIPT KILL
就可以直接終止腳本執(zhí)行,不用擔(dān)心數(shù)據(jù)被修改。
但是,如果腳本已經(jīng)改寫了數(shù)據(jù)內(nèi)容,SCRIPT KILL
將報出以下錯誤,因為它破壞數(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í)行了寫命令,SCRIPT KILL
將無法執(zhí)行。那我們就只剩以下兩種選擇了:
- 繼續(xù)等待腳本執(zhí)行完成
- 使用
SHUTDOWN NOSAVE
來直接停掉 Redis,并避免臟數(shù)據(jù)持久化到磁盤
最后,不知道你有沒有疑問,從開始執(zhí)行腳本到 SHUTDOWN 之間的寫命令會把日志寫到AOF里嗎?Lua腳本中的命令什么時候會寫AOF里?
講道理,既然 Redis 為了不破壞腳本的原子性而不讓SCRIPT KILL
執(zhí)行,那么腳本中寫命令的 “提交” 也應(yīng)當(dāng)是原子執(zhí)行的,而不是執(zhí)行一句就向AOF里寫一句。
“提交”:借用數(shù)據(jù)庫中 commit 的概念,這里指寫入AOF文件中
下面就來驗證這個猜測:
先執(zhí)行 tail -f appendonly.aof
實時查看AOF文件變化
再開一個redis-cli 命令行執(zhí)行一個內(nèi)容如下的Lua腳本
redis.call('set','a','aaaa') --先執(zhí)行寫命令 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)象是,腳本剛開始執(zhí)行,AOF文件毫無反應(yīng),一直等到8秒后腳本完成,命令才追加寫入到AOF中。
這就驗證了Redis腳本里的寫命令是等到執(zhí)行完成后再一次性寫入AOF的。
參考
到此這篇關(guān)于Redis中Lua腳本的使用和設(shè)置超時 的文章就介紹到這了,更多相關(guān)Redis Lua 超時內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis?存儲對象信息用?Hash?和String的區(qū)別
這篇文章主要介紹了Redis存儲對象信息用Hash和String的區(qū)別,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-09-09簡介Lua腳本與Redis數(shù)據(jù)庫的結(jié)合使用
這篇文章主要介紹了簡介Lua腳本與Redis數(shù)據(jù)庫的結(jié)合使用,Redis是基于主存的高性能數(shù)據(jù)庫,需要的朋友可以參考下2015-06-06AOP?Redis自定義注解實現(xiàn)細(xì)粒度接口IP訪問限制
這篇文章主要為大家介紹了AOP?Redis自定義注解實現(xiàn)細(xì)粒度接口IP訪問限制,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10關(guān)于Redis網(wǎng)絡(luò)模型的源碼詳析
這篇文章主要給大家介紹了關(guān)于Redis網(wǎng)絡(luò)模型的源碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者使用Redis具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07Linux、Windows下Redis的安裝即Redis的基本使用詳解
Redis是一個基于內(nèi)存的key-value結(jié)構(gòu)數(shù)據(jù)庫,Redis 是互聯(lián)網(wǎng)技術(shù)領(lǐng)域使用最為廣泛的存儲中間件,這篇文章主要介紹了Linux、Windows下Redis的安裝即Redis的基本使用詳解,需要的朋友可以參考下2022-09-09