【Redis】Lua脚本在Redis中的基本使用及其原子性保证原理

文章目录

背景

Lua 本身是一种轻量小巧的脚本语言,在Redis2.6版本开始引入了对Lua脚本的支持。通过在服务器中嵌入Lua环境,Redis客户端就可以使用Lua脚本,直接在服务器端原子地执行多个Redis命令。在Redis中Lua有两种执行方式:Eval和EvalSHA。

一、Eval

通过Redis内置的 Lua 解释器,可以使用 EVAL 命令(也可以使用redis-cli 的--eval 参数)对 Lua 脚本进行解析。需要注意的点是执行Lua也会使Redis阻塞。

Eval命令的执行过程主要可以分为三个步骤:

  • 根据客户端给定的Lua脚本,在Lua环境中定义一个Lua函数,Lua函数的名称实际上是f_为前缀加上脚本本身计算出来的SHA1值组成,如f_ddfsdfjgjbg33rndgj00,SHA1的长度是40字符,而函数体则是脚本本身。
  • 将客户端给定的脚本保存到lua_scripts字典,简单来说就是添加一个key-value,key就是Lua脚本的SHA1校验和,而值是Lua脚本本身,这主要是用于以后使用。
  • 执行第一步在Lua环境中定义的函数,以此来执行客户端中给定的Lua脚本。

在Redis中,使用了Key列表和参数列表来为Lua脚本提供更多的灵活性,执行Eval命令的格式为:

shell 复制代码
eval  脚本内容 key个数 key列表 参数列表

栗子1:

lua 复制代码
eval "return 'hello lua'" 0

栗子2:

lua 复制代码
127.0.0.1:6379> eval 'return "hello " .. KEYS[1] .. ARGV[1]' 1 redis world

执行返回:"hello world"

二、EvalSHA

EvalSHA 中方式的是拆分成两个步骤,首先要将Lua脚本加载到Redis服务端,得到该脚本的SHA1校验码。然后使用EvalSHA命令使用SHA1作为参数可以直接执行对应Lua脚本。这样做的好处是可以避免每次发送Lua脚本的开销,而脚本也会常驻在服务端,脚本功能得到了复用。缺点是要怎么管理这些脚本和命令过多的话会占用Redis的内存。

在介绍Eval命令执行过程中,第一步会在Lua环境中生成一个Lua脚本对应的函数,形如:f_dfdugndgub320433,只要脚本对应的函数在Lua中定义过,那么即使不知道脚本的内容本身,客户端也是可以根据脚本的SHA1来调用脚本对应的函数,从而达到执行脚本的目的,这也就是EvalSHA命令的实现原理。

EvalSHA命令的一般格式:

lua 复制代码
evalsha sha1值 key个数 key列表 参数列表

如:

shell 复制代码
eval "return 'hello lua'" 0
// 执行完eval后,Lua环境就定义了一个函数名为:f_dfaujjgdgu388vdf83803(),那么就可以根据对应的sha1值来调用函数了。
evlsha "dfaujjgdgu388vdf83803" 0

三、Redis 对 Lua 脚本的管理

除了Eval和EvalSHA命令之外,Redis中与Lua脚本相关的命令还有:script flushscript existsscript load以及script kill

3.1 script flush

script flush命令用于清除服务器中的所有和Lua脚本相关的信息,这个命令会释放并重建lua_scripts字典,关闭现有的Lua环境并重建一个新的Lua环境。

命令格式:

shell 复制代码
script flush

栗子:

shell 复制代码
127.0.0.1:6379> script flush
OK

3.2 script exists

script exists命令会根据输入的SHA1校验和,检查校验和对应的脚本是否存在于服务器中。该命令主要是通过检查给定的SHA1是否存在于lua_scripts字典来实现的。

命令格式:

shell 复制代码
script exists sha1[sha1...]

返回结果代表 sha1[sha1...]被加载到 Redis 内存的个数。

栗子:

shell 复制代码
127.0.0.1:6379> script exists 5ea77eda7a16440abe244e6a88fd9df204ecd5aa
1) (integer) 1

3.3 script load

script load命令和Eval命令执行Lua脚本的前两步完全一样,该命令首先在Lua环境中创建相对应的函数,然后再将脚本保存到lua_scripts字典中。

命令格式:

shell 复制代码
script load lua脚本

栗子:

shell 复制代码
127.0.0.1:6379> script load "return 'hello lua'"
"5ea77eda7a16440abe244e6a88fd9df204ecd5aa"

3.4 script kill

如果Lua脚本比较耗时,甚至Lua脚本存在问题,那么此时Lua脚本的执行会阻塞Redis,直到脚本执行完毕或 者外部进行干预将其结束,就可以使用script kill来杀掉正在执行的 Lua 脚本。

举个栗子:

shell 复制代码
## 写一个死循环的lua脚本并在redis客户端执行
127.0.0.1:6379> eval 'while 1==1 do end' 0
## 重新起一个客户端去执行命令,返回了报错信息,此时Redis已经阻塞,无法处理正常的调用,这时可以选择继续等待。
## 但更多时候需要快速将脚本杀掉。使用shutdown save显然不太合适,所以选择script kill,当script kill执行之后,客户端调用会恢复
127.0.0.1:6379> get test
(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
## 另起一个客户端,停止脚本执行
127.0.0.1:6379> script kill
OK
127.0.0.1:6379> get k1
"11"

此外,Redis提供了一个lua-time-limit参数,用于配置Lua脚本执行的超时时间 ,当 Lua 脚本时间超过lua-time-limit后,向其他命令调用发送BUSY的信号,并不会停止掉服务端和客户端的脚本执行 ,所以当某个Lua脚本执行达到lua-time-limit值之后,其他客户端在执行正常的命令时,将会收到"Busy Redis is busy running a script"错误,并且提示使用script kill或者shutdown nosave命令来杀掉这个 busy 的脚本。

实际上,如果Redis服务端设置了lua-time-limit参数,那么在每次执行Lua脚本之前,服务器都会在Lua环境里面设置一个超时的处理钩子(hook)。超时处理钩子再脚本运行期间,会定期检查脚本已经运行了多长时间,一旦钩子发现脚本的运行时间已经超过了lua-time-limit设定的时长,那么钩子将定期在脚本运行的间隙中,查看是否有script kill或者shutdown nosave到达服务器。如果超时运行的脚本未执行过任何的写入操作,那么客户端可以通过script kill命令停止脚本的运行,并向执行该脚本的客户端发送一个错误回复。处理完script kill命令后服务器可以继续运行。

此外,如果超时脚本已经执行过写入操作,那么客户端只能用shutdown nosave命令来停止服务器,从而防止不合法的数据被写入。

四、Lua在Redis中原子性执行的原理

在Redis中,Lua脚本能够保证原子性的主要原因还是Redis采用了单线程执行模型。也就是说,当Redis执行Lua脚本时,Redis会把Lua脚本作为一个整体并把它当作一个任务加入到一个队列中,然后单线程按照队列的顺序依次执行这些任务,在执行过程中Lua脚本是不会被其他命令或请求打断,因此可以保证每个任务的执行都是原子性的。

这块儿想要继续了解的可以参考:为什么Redis单线程却能支撑高并发?Redis6.0之后为什么又引入多线程?

在实际开发中,为了提高效率,通常我们能让lua脚本接收多个参数,一次发送到Redis服务端,还能将key进行整理,将相同slot的key放到同一批进行处理,这在性能优化中能减少网络传输的开销,同时也能并发的发送给Redis服务端。

相关推荐
笑远5 小时前
MySQL 主主复制与 Redis 环境安装部署
redis·mysql·adb
小斌的Debug日记7 小时前
框架基本知识总结 Day16
redis·spring
勘察加熊人7 小时前
fastapi房产销售系统
数据库·lua·fastapi
morris1318 小时前
【redis】布隆过滤器的Java实现
java·redis·布隆过滤器
椰椰椰耶8 小时前
【redis】全局命令set、get、keys
数据库·redis·缓存
月落星还在8 小时前
Redis 内存淘汰策略深度解析
数据库·redis·缓存
五行星辰8 小时前
Java链接redis
java·开发语言·redis
左灯右行的爱情8 小时前
Redis- 切片集群
数据库·redis·缓存
周小闯9 小时前
Easyliev在线视频分享平台项目总结——SpringBoot、Mybatis、Redis、ElasticSearch、FFmpeg
spring boot·redis·mybatis
交换机路由器测试之路11 小时前
【资料分享】wireshark解析脚本omci.lua文件20250306版本发布(独家分享)
网络协议·测试工具·wireshark·lua·omci