在Redis中使用lua
在 Redis 中使用 lua,有如下三个优点:
- 可以批量执行命令。一次性发送多个命令,减少网络开销。
- 原子性。Redis将整个lua脚本作为一个整体执行,保证了原子性。
- 可以复用命令。对于复杂的命令组合,可以放到文件中,从而实现命令复用。
1、在Redis中可以使用eval命令执行lua脚本
eval 命令的语法格式如下:
EVAL script numkeys [key [key ...]] [arg [arg ...]]
- script:表示 lua 脚本内容
- numKeys:表示全局变量 KEYS 的数量,如果没有全局变量 KEYS,则为 0
- key…:(可选参数)表示参数,下标从 1 开始
- value…:(可选参数)表示参数值,下标从 1 开始
eval "return 'hello world'" 0
2、lua脚本中也可以调用Redis命令
语法格式:
redis.call(command, key [, param ...])
- command:Redis 命令,记得外层使用单引号
- key:被操作的键
- param:被操作的键对应的值
eval "redis.call('set', 'hello', 'world')" 0
eval "redis.call('set', KEYS[1], ARGV[1])" 1 hello world
3、Redis执行lua文件
比如 hello.lua 文件的内容如下:
local value = redis.call('exists', KEYS[1])
if (value ~= 0) then
value = tonumber(redis.call('get', KEYS[1]))
else
value = 1
end
value = value * tonumber(ARGV[1])
redis.call('set', KEYS[1], value)
return value
然后在 Redis 中执行该文件:
redis-cli -h 10.211.55.11 -p 6379 -a 123 --eval "hello.lua" hello , 2
注意空格
5 秒限制访问 10 次(成功返回 1;失败返回 0)
local num = redis.call('incr', KEYS[1])
if tonumber(num) == 1 then
redis.call('expire', KEYS[1], ARGV[1])
return 1
elseif tonumber(num) > tonumber(ARGV[2])
return 0
else
return 0
end
然后在 Redis 中执行该文件:
redis-cli -h 10.211.55.11 -p 6379 -a 123 --eval ip_limit.lua app:ip:limit:192.168.0.106 , 5 10
注意空格
缓存lua脚本
在 lua 脚本内容比较长的情况下,如果每次调用脚本都需要将整个脚本传给 Redis 服务端,会产生比较大的网络开销。
为了解决这个问题,Redis 可以缓存 lua 脚本并生成 SHA1 摘要码,后续可以直接使用这个摘要码来执行 lua 脚本。
语法格式
SCRIPT LOAD <script>
EVALSHA <sha1> <numkeys> [key [key ...]] [arg [arg ...]]
比如执行一个输出 hello 的脚本。
script load "return 'hello'"
evalsha "1b936e3fe509bcbc9cd0664897bbe8fd0cac101b" 0
再比如实现自乘 2 的功能。
local curVal = redis.call('get', KEYS[1])
if curVal == false then
curVal = 0
else
curVal = tonumber(curVal)
end
curVal = curVal * tonumber(ARGV[1])
redis.call('set', KEYS[1], curVal)
return curVal
然后把这个脚本变成单行,语句之间用分号分割。
local curVal = redis.call('get', KEYS[1]); if curVal == false then curVal = 0 else curVal = tonumber(curVal) end; curVal = curVal * tonumber(ARGV[1]); redis.call('set', KEYS[1], curVal); return curVal
接着调用 script load 命令,缓存这个lua脚本。
script load "local curVal = redis.call('get', KEYS[1]); if curVal == false then curVal = 0 else curVal = tonumber(curVal) end; curVal = curVal * tonumber(ARGV[1]); redis.call('set', KEYS[1], curVal); return curVal"
evalsha "1b936e3fe509bcbc9cd0664897bbe8fd0cac101c" 1 num 2
脚本超时
执行如下命令会导致其它命令进入等待状态。
eval "while true do end" 0
Redis 对于脚本执行有一个超时时间,默认 5 秒(lua-time-limit 5000)。超过 5 秒,其它客户端的命令不会再等待,而是直接返回“BUSY”错误。该脚本过了 5 秒超时时间并不会停止执行。
有两个命令可以终止脚本的执行:script kill、shutdown nosave。
但是并不是所有的 lua 脚本执行都可以用 script kill 命令终止,如果 lua 脚本执行的是对 Redis 数据进行修改操作,为了保证脚本执行的原子性,导致 script kill 命令不会终止脚本的执行。
eval "redis.call('set', 'hello', 'world') while true do end" 0
对此,可以使用 shutdown nosave 命令,直接将 Redis 服务停掉。
shutdown nosave 与 shutdown 的区别是:它不会进行持久化操作,意味着上一次快照之后的数据库修改操作都会丢失。