Redis架构篇 - Redis与Lua脚本

在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 的区别是:它不会进行持久化操作,意味着上一次快照之后的数据库修改操作都会丢失。

猜你喜欢

转载自blog.csdn.net/qq_34561892/article/details/129160662
今日推荐