redis中l(wèi)ua腳本使用教程
一、背景
在使用redis
的過程中,發(fā)現(xiàn)有些時(shí)候需要原子性
去操作redis命令,而redis的lua
腳本正好可以實(shí)現(xiàn)這一功能。比如: 扣減庫(kù)存操作、限流操作等等。
redis的pipelining
雖然也可以一次執(zhí)行一組命令,但是如果在這一組命令的執(zhí)行過程中,需要根據(jù)上一步執(zhí)行的結(jié)果做一些判斷,則無法實(shí)現(xiàn)。
二、使用lua腳本
Redis中使用的是 Lua 5.1
的腳本規(guī)范,同時(shí)我們編寫的腳本的時(shí)候,不需要定義 Lua 函數(shù)。同時(shí)也不能使用全局變量等等。
1、lua腳本的格式和注意事項(xiàng)
1、格式
EVAL script numkeys key [key ...] arg [arg ...]
127.0.0.1:6379> eval "return {KEYS[1],ARGV[1],ARGV[2]}" 1 key1 arg1 arg2
1) "key1"
2) "arg1"
3) "arg2"
127.0.0.1:6379>
2、注意事項(xiàng)
Lua
腳本中的redis操作的key最好都是通過 KEYS
來傳遞,而不要寫死。否則在Redis Cluster的情況下可能有問題.
1、好的寫法
127.0.0.1:6379> eval "return redis.call('set',KEYS[1],'zhangsan')" 1 username
OK
127.0.0.1:6379> get username
"zhangsan"
redis命令操作的key是通過KEYS獲取的。
2、差的寫法
127.0.0.1:6379> eval "return redis.call('set','username','zhangsan')" 0
OK
127.0.0.1:6379> get username
"zhangsan"
redis命令操作的key是直接寫死的。
2、將腳本加載到redis中
需求: 此處定義一個(gè)lua腳本,將輸入的參數(shù)的值+1返回。
注意:
當(dāng)我們把 lua腳本
加載到redis中,這個(gè)腳本并不會(huì)馬上執(zhí)行,而是會(huì)緩存起來,并且返回sha1
校驗(yàn)和,后期我們可以通過 EVALSHA
來執(zhí)行這個(gè)腳本。
此處我們記住這個(gè)腳本加載后返回的hash值,在下一步執(zhí)行的時(shí)候需要用到。
127.0.0.1:6379> script load "return tonumber(KEYS[1])+1"
"ef424d378d47e7a8b725259cb717d90a4b12a0de"
127.0.0.1:6379>
3、執(zhí)行l(wèi)ua腳本
1、通過eval執(zhí)行
127.0.0.1:6379> eval "return tonumber(KEYS[1]) + 1" 1 100
(integer) 101
127.0.0.1:6379>
2、通過evalsha執(zhí)行
ef424d378d47e7a8b725259cb717d90a4b12a0de
的值為上一步通過 script load
加載腳本后獲取的。
127.0.0.1:6379> evalsha ef424d378d47e7a8b725259cb717d90a4b12a0de 1 100
(integer) 101
127.0.0.1:6379>
通過 evalsha
執(zhí)行的好處是可以節(jié)省帶寬。如果我們的lua腳本比較長(zhǎng),程序在執(zhí)行的時(shí)候?qū)ua腳本發(fā)送到redis服務(wù)器則可能耗費(fèi)的帶寬多,如果發(fā)送的是hash值的話,則耗費(fèi)的帶寬少。
4、判斷腳本是否在redis服務(wù)器緩存中
127.0.0.1:6379> script load "return tonumber(KEYS[1])+1"
"ef424d378d47e7a8b725259cb717d90a4b12a0de"
127.0.0.1:6379> script exists ef424d378d47e7a8b725259cb717d90a4b12a0de
1) (integer) 1
127.0.0.1:6379> script exists not-exists-sha1
1) (integer) 0
127.0.0.1:6379>
5、清空服務(wù)器上的腳本緩存
注意:
我們無法清除某一個(gè)腳本的緩存,只可以清楚所有的緩存,一般情況下沒有必要清楚,因?yàn)榧词褂写罅康哪_本也不會(huì)太占用服務(wù)器內(nèi)存。
127.0.0.1:6379> script load "return tonumber(KEYS[1])+1"
"ef424d378d47e7a8b725259cb717d90a4b12a0de"
127.0.0.1:6379> script exists ef424d378d47e7a8b725259cb717d90a4b12a0de
1) (integer) 1
127.0.0.1:6379> script flush
OK
127.0.0.1:6379> script exists ef424d378d47e7a8b725259cb717d90a4b12a0de
1) (integer) 0
6、殺死正在運(yùn)行的腳本
127.0.0.1:6379> script kill
注意:
- 該命令只可以殺死正在運(yùn)行的
只讀腳本
。 - 對(duì)于修改了數(shù)據(jù)的腳本,無法使用此命令殺死,只能使用
shutdown nosave
命令。 - 腳本執(zhí)行的
默認(rèn)超時(shí)時(shí)間
為5分鐘
,可以通過redis.conf
配置文件的lua-time-limit
配置項(xiàng)修改。 - 腳本即使到達(dá)了超時(shí)時(shí)間,也不會(huì)停止執(zhí)行,因?yàn)檫@違反了lua腳本的原子性。
三、lua和redis數(shù)據(jù)類型轉(zhuǎn)換
Lua
的數(shù)據(jù)類型和Redis
的數(shù)據(jù)類型存在一對(duì)一的轉(zhuǎn)換關(guān)系,如果將Redis類型轉(zhuǎn)換成Lua類型,然后在轉(zhuǎn)換成Redis類型,那么結(jié)果和初試值是一致的。
1、類型轉(zhuǎn)換
Redis to Luaconversion table.
- Redis integer reply -> Lua number
- Redis bulk reply -> Lua string
- Redis multi bulk reply -> Lua table (may have other Redis data types nested)
- Redis status reply -> Lua table with a single ok field containing the status
- Redis error reply -> Lua table with a single err field containing the error
- Redis Nil bulk reply and Nil multi bulk reply -> Lua false boolean type
Lua to Redisconversion table.
- Lua number -> Redis integer reply (the number is converted into an integer)
- Lua string -> Redis bulk reply
- Lua table (array) -> Redis multi bulk reply (truncated to the first nil inside the Lua array if any)
- Lua table with a single ok field -> Redis status reply
- Lua table with a single err field -> Redis error reply
- Lua boolean false -> Redis Nil bulk reply.
2、額外的轉(zhuǎn)換規(guī)則
- Lua的布爾類型,Lua的True會(huì)轉(zhuǎn)換成Redis的1
3、3個(gè)重要規(guī)則
1. 數(shù)字類型
在Lua中,只有一個(gè)number
類型,整數(shù)和浮點(diǎn)數(shù)之間沒有區(qū)別,如果我們?cè)贚ua中返回一個(gè)浮點(diǎn)數(shù),實(shí)際返回的是一個(gè)整數(shù),如果要返回浮點(diǎn)數(shù),需要以字符串的方式返回。
127.0.0.1:6379> eval "return 3.98" 0
(integer) 3
127.0.0.1:6379> eval "return '3.98'" 0
"3.98"
2. lua數(shù)組存在nil
當(dāng) Redis 將 Lua 數(shù)組轉(zhuǎn)換為 Redis 協(xié)議時(shí),如果遇到 nil,則轉(zhuǎn)換會(huì)停止。即 nil 后的值都不會(huì)返回。
127.0.0.1:6379> eval "return {1,2,'data',nil,'can not return value','vv'}" 0
1) (integer) 1
2) (integer) 2
3) "data"
127.0.0.1:6379>
3. Lua的Table類型包含建和值
出現(xiàn)這種情況返回的redis的是一個(gè)空數(shù)組
127.0.0.1:6379> eval "return {key1 ='value1',key2='value2'}" 0
(empty array)
127.0.0.1:6379>
四、lua腳本中輸出日志
這個(gè)一般調(diào)試我們的腳本的時(shí)候比較有用。
redis.log(loglevel,message)
loglevel的取值范圍:
redis.LOG_DEBUG
redis.LOG_VERBOSE
redis.LOG_NOTICE
redis.LOG_WARNING
舉例:
五、一個(gè)簡(jiǎn)單限流的案例
1、需求
在 1s 之內(nèi),方法最大的并發(fā)只能是 5。
1s 和 5 當(dāng)作參數(shù)傳遞。
2、實(shí)現(xiàn)步驟
1、編寫lua腳本
-- 輸出用戶傳遞進(jìn)來的參數(shù) for i, v in pairs(KEYS) do redis.log(redis.LOG_NOTICE, "limit: key" .. i .. " = " .. v) end for i, v in pairs(ARGV) do redis.log(redis.LOG_NOTICE, "limit: argv" .. i .. " = " .. v) end -- 限流的key local limitKey = tostring(KEYS[1]) -- 限流的次數(shù) local limit = tonumber(ARGV[1]) -- 多長(zhǎng)時(shí)間過期 local expireMs = tonumber(ARGV[2]) -- 當(dāng)前已經(jīng)執(zhí)行的次數(shù) local current = tonumber(redis.call('get', limitKey) or '0') -- 設(shè)置一個(gè)斷點(diǎn) redis.breakpoint() redis.log(redis.LOG_NOTICE, "limit key: " .. tostring(limitKey) .. " 在[" .. tostring(expireMs) .. "]ms內(nèi)已經(jīng)訪問了 " .. tostring(current) .. " 次,最多可以訪問: " .. limit .. " 次") -- 限流了 if (current + 1 > limit) then return { true } end -- 未達(dá)到訪問限制 -- 訪問次數(shù)+1 redis.call("incrby", limitKey, "1") if (current == 0) then -- 設(shè)置過期時(shí)間 redis.call("pexpire", limitKey, expireMs) end return { false }
2、程序中執(zhí)行l(wèi)ua腳本
完整代碼: https://gitee.com/huan1993/spring-cloud-parent/tree/master/springboot/springboot-redis-lua
六、lua腳本的debug
當(dāng)我們編寫好了lua腳本后,如果在執(zhí)行的過程中發(fā)生了錯(cuò)誤,那么我們?nèi)绾卧撊绾谓鉀Q呢?此處我們來了解下如何debug lua 腳本。
1、lua腳本中的幾個(gè)小命令
在 腳本中打一個(gè)斷點(diǎn)
redis.breakpoint()
2、斷點(diǎn)調(diào)試
1、執(zhí)行命令
redis-cli --ldb --eval limit.lua invoked , 1 1000 limit.lua 需要debug的lua文件 invoked 為傳遞到 lua 腳本中 KEYS 的值 1 和 1000 為傳遞到 lua 腳本中 ARGV 的值 , 分割 出 KEYS 和 ARGV 的值
2、一些debug指令
help
: 列出可用的debug指令s
或n
: 運(yùn)行到當(dāng)前行并停止 (此時(shí)當(dāng)前行還未執(zhí)行)c
:運(yùn)行到下個(gè)斷點(diǎn),即運(yùn)行到lua腳本中存在redis.breakpoint()
方法的地方list
:列出當(dāng)前行周圍的一些源碼p
:打印出所有的 local 變量的值p <var>
:打印具體的某個(gè) local 變量的值r
:執(zhí)行 redis 命令
-- eg:
r set key value
r get key
3、debug運(yùn)行結(jié)果
七、參考文檔
https://redis.io/commands/eval
到此這篇關(guān)于redis中l(wèi)ua腳本的簡(jiǎn)單使用的文章就介紹到這了,更多相關(guān)redis中l(wèi)ua腳本使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- redis使用Lua腳本解決多線程下的超賣問題及原因解析
- Redis中l(wèi)ua腳本實(shí)現(xiàn)及其應(yīng)用場(chǎng)景
- Java生態(tài)/Redis中使用Lua腳本的過程
- springboot使用redisTemplate操作lua腳本
- springboot中使用redis并且執(zhí)行調(diào)試lua腳本
- Redis調(diào)用Lua腳本及使用場(chǎng)景快速掌握
- Redis中Lua腳本的使用和設(shè)置超時(shí)
- redis執(zhí)行l(wèi)ua腳本的實(shí)現(xiàn)方法
- Redis中Lua腳本的使用場(chǎng)景示例分析
相關(guān)文章
Redis設(shè)置Hash數(shù)據(jù)類型的過期時(shí)間
在Redis中,我們可以使用Hash數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ)一組鍵值對(duì),而有時(shí)候,我們可能需要設(shè)置這些鍵值對(duì)的過期時(shí)間,本文主要介紹了Redis設(shè)置Hash數(shù)據(jù)類型的過期時(shí)間,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01Redis中什么是Big?Key(大key)問題?如何解決Big?Key問題?
大key并不是指key的值很大,而是key對(duì)應(yīng)的value很大,下面這篇文章主要給大家介紹了Redis中什么是Big?Key(大key)問題?如何解決Big?Key問題的相關(guān)資料,需要的朋友可以參考下2023-03-03在Redis集群中使用pipeline批量插入的實(shí)現(xiàn)方法
這篇文章主要介紹了在Redis集群中使用pipeline批量插入的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05用Redis實(shí)現(xiàn)微博關(guān)注關(guān)系
在微博中,每一個(gè)用戶都會(huì)有一個(gè)關(guān)注列表,一個(gè)粉絲列表。用戶可以查看自己的關(guān)注,粉絲列表,也可以查看別人的關(guān)注,粉絲列表。并且,要展示列表里每個(gè)人與當(dāng)前查看者的關(guān)注狀態(tài)。2015-09-09利用控制臺(tái)如何對(duì)Redis執(zhí)行增刪改查命令
這篇文章主要給大家介紹了關(guān)于利用控制臺(tái)如何對(duì)Redis執(zhí)行增刪改查命令的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08Redis實(shí)現(xiàn)IP限流的2種方式舉例詳解
通俗的說限流就是限制一段時(shí)間內(nèi)用戶訪問資源的次數(shù),減輕服務(wù)器壓力,這篇文章主要給大家介紹了關(guān)于Redis實(shí)現(xiàn)IP限流的2種方式,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2024-08-08淺談Redis位圖(Bitmap)及Redis二進(jìn)制中的問題
這篇文章主要介紹了Redis位圖(Bitmap)及Redis二進(jìn)制中的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07