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實(shí)現(xiàn)請(qǐng)求限制與速率限制
API速率限制(Rate Limiting)是控制用戶訪問(wèn)API的請(qǐng)求速率的一種機(jī)制,防止系統(tǒng)被過(guò)多請(qǐng)求淹沒(méi),下面我們來(lái)看看如何使用Redis和FastAPI實(shí)現(xiàn)請(qǐng)求限制與速率控制吧2025-04-04
redis簡(jiǎn)介_(kāi)動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了redis簡(jiǎn)介,Redis是一個(gè)開(kāi)源的,先進(jìn)的 key-value 存儲(chǔ)可用于構(gòu)建高性能,可擴(kuò)展的 Web 應(yīng)用程序的解決方案,有興趣的可以了解一下2017-08-08
一文搞懂Redis中的慢查詢?nèi)罩竞捅O(jiān)視器
我們都知道MySQL有慢查詢?nèi)罩?但Redis也有慢查詢?nèi)罩?可用于監(jiān)視和優(yōu)化查詢,本文給大家詳細(xì)介紹了Redis中的慢查詢?nèi)罩竞捅O(jiān)視器,文章通過(guò)代碼示例講解的非常詳細(xì),需要的朋友可以參考下2024-04-04
Redis實(shí)戰(zhàn)之百度首頁(yè)新聞熱榜的實(shí)現(xiàn)代碼
這篇文章主要介紹了Redis實(shí)戰(zhàn)之百度首頁(yè)新聞熱榜的實(shí)現(xiàn)代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02

