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