Redis中Lua腳本的使用和設(shè)置超時(shí)
Redis提供了Lua腳本功能來(lái)讓用戶實(shí)現(xiàn)自己的原子命令,但也存在著風(fēng)險(xiǎn),編寫不當(dāng)?shù)哪_本可能阻塞線程導(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ù)集單線程讀寫的特性,Lua腳本執(zhí)行時(shí)會(huì)阻塞所有對(duì)數(shù)據(jù)集的讀寫操作,這給它帶來(lái)了下面兩個(gè)特性:
- 原子性:可以通過(guò)Lua腳本實(shí)現(xiàn)對(duì)數(shù)據(jù)集的原子讀寫操作,這和Redis的事務(wù)功能
MULTI / EXEC類似 - 長(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é)果保存到客戶端輸出緩沖區(qū),等待服務(wù)器將結(jié)果返回客戶端
- 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 5000Lua腳本最大執(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)始允許接受其他客戶端請(qǐng)求,但僅限于 SCRIPT KILL 和 SHUTDOWN NOSAVE 兩個(gè)命令
其他請(qǐng)求仍返回busy錯(cuò)誤
SCRIPT KILL 命令
如果Lua只是讀取數(shù)據(jù)而沒(méi)做修改的話,執(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將無(wú)法執(zhí)行。那我們就只剩以下兩種選擇了:
- 繼續(xù)等待腳本執(zhí)行完成
- 使用
SHUTDOWN NOSAVE來(lái)直接停掉 Redis,并避免臟數(shù)據(jù)持久化到磁盤
最后,不知道你有沒(méi)有疑問(wèn),從開(kāi)始執(zhí)行腳本到 SHUTDOWN 之間的寫命令會(huì)把日志寫到AOF里嗎?Lua腳本中的命令什么時(shí)候會(huì)寫AOF里?
講道理,既然 Redis 為了不破壞腳本的原子性而不讓SCRIPT KILL執(zhí)行,那么腳本中寫命令的 “提交” 也應(yīng)當(dāng)是原子執(zhí)行的,而不是執(zhí)行一句就向AOF里寫一句。
“提交”:借用數(shù)據(jù)庫(kù)中 commit 的概念,這里指寫入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í)行寫命令
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秒后腳本完成,命令才追加寫入到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)文章希望大家以后多多支持腳本之家!
- redis使用Lua腳本解決多線程下的超賣問(wèn)題及原因解析
- Redis中l(wèi)ua腳本實(shí)現(xiàn)及其應(yīng)用場(chǎng)景
- Java生態(tài)/Redis中使用Lua腳本的過(guò)程
- springboot使用redisTemplate操作lua腳本
- springboot中使用redis并且執(zhí)行調(diào)試lua腳本
- Redis調(diào)用Lua腳本及使用場(chǎng)景快速掌握
- redis執(zhí)行l(wèi)ua腳本的實(shí)現(xiàn)方法
- redis中l(wèi)ua腳本使用教程
- Redis中Lua腳本的使用場(chǎng)景示例分析
相關(guān)文章
Redis?存儲(chǔ)對(duì)象信息用?Hash?和String的區(qū)別
這篇文章主要介紹了Redis存儲(chǔ)對(duì)象信息用Hash和String的區(qū)別,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09
簡(jiǎn)介L(zhǎng)ua腳本與Redis數(shù)據(jù)庫(kù)的結(jié)合使用
這篇文章主要介紹了簡(jiǎn)介L(zhǎng)ua腳本與Redis數(shù)據(jù)庫(kù)的結(jié)合使用,Redis是基于主存的高性能數(shù)據(jù)庫(kù),需要的朋友可以參考下2015-06-06
AOP?Redis自定義注解實(shí)現(xiàn)細(xì)粒度接口IP訪問(wèn)限制
這篇文章主要為大家介紹了AOP?Redis自定義注解實(shí)現(xiàn)細(xì)粒度接口IP訪問(wèn)限制,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
redis數(shù)據(jù)一致性之延時(shí)雙刪策略詳解
在使用redis時(shí),需要保持redis和數(shù)據(jù)庫(kù)數(shù)據(jù)的一致性,最流行的解決方案之一就是延時(shí)雙刪策略,今天我們就來(lái)詳細(xì)刨析一下,需要的朋友可以參考下2023-09-09
使用Redis實(shí)現(xiàn)用戶積分排行榜的教程
這篇文章主要介紹了使用Redis實(shí)現(xiàn)用戶積分排行榜的教程,包括一個(gè)用PHP腳本進(jìn)行操作的例子,需要的朋友可以參考下2015-04-04
Redis實(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ò)模型的源碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用Redis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
Linux、Windows下Redis的安裝即Redis的基本使用詳解
Redis是一個(gè)基于內(nèi)存的key-value結(jié)構(gòu)數(shù)據(jù)庫(kù),Redis 是互聯(lián)網(wǎng)技術(shù)領(lǐng)域使用最為廣泛的存儲(chǔ)中間件,這篇文章主要介紹了Linux、Windows下Redis的安裝即Redis的基本使用詳解,需要的朋友可以參考下2022-09-09

