10. lua脚本
其实在《3.7.1 单机Redis的分布式锁》的一段python代码中已经使用到了lua脚本,当时是为了解决在Redis中取值和比对的原子性问题。那么Redis中的lua脚本具体有什么特点呢?我们一起来看看。
-
lua脚本在Redis中执行时是原子的。
也正是因为这个特性,我们要防止lua脚本执行时长过大,导致Redis卡顿。为了防止这个问题,脚本还有一个最大执行时间限制,默认值是 5 秒钟。当然我们也可以通过redis.conf文件中的lua-time-limit 选项(单位:毫秒)来控制这个时间。
-
我们写的lua脚本中一定会有对于Redis的相关操作,这时就用到了一个伪客户端。
请求方式:客户端 ---> lua环境 ---> 伪客户端 ---> 命令执行器
给客户端的感觉只是和Redis交互了其实中间还有其他的步骤。我们可以用redis.call()或者redis.pcall()在lua脚本中内嵌Redis相关操作
-
对于同样的输入集,Redis执行得到的结果是一致的
Redis对lua脚本的支持其实并不是全量的,它只引入了一部分lua的函数库(可以看官方文档来查阅)。Reids会阻止有不确定性的lua脚本执行,比如一些随机函数或者时间函数,因为这些函数可能导致Redis在不同时间段执行同样输入集的脚本得到不同的结果。并且在有序数据输出的时候,Redis还会对输出结果进行辅助排序,保证每次得到的结果是一致的。
10.1 lua相关命令
-
EVAL
EVAL的第一个参数是一段 Lua 5.1 脚本程序。 这段Lua脚本不需要(也不应该)定义函数。它运行在 Redis 服务器中。
EVAL的第二个参数是参数的个数,后面的参数(从第三个参数),表示在脚本中所用到的那些 Redis 键(key),这些键名参数可以在 Lua 中通过全局变量 KEYS 数组,用 1 为基址的形式访问( KEYS[1] , KEYS[2] ,以此类推)。
在命令的最后,那些不是键名参数的附加参数 arg [arg …] ,可以在 Lua 中通过全局变量 ARGV 数组访问,访问的形式和 KEYS 变量类似( ARGV[1] 、 ARGV[2] ,诸如此类)。
> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second 1) "key1" 2) "key2" 3) "first" 4) "second"
-
EVALSHA
根据给定的 SHA1 校验码,对缓存在服务器中的脚本进行求值,如果在缓存中没有该脚本,就会有NOSCRIPT错误。 将脚本缓存到服务器的操作可以通过 SCRIPT LOAD 命令进行。 这个命令的其他地方,比如参数的传入方式,都和EVAL命令一样。
-
SCRIPT EXISTS
检查脚本是否存在脚本缓存里面。
这个命令可以接受一个或者多个脚本SHA1信息,返回一个1或者0的列表,如果脚本存在或不存在。
还可以使用管道技术(pipelining operation)确保脚本加载(也可以使用SCRIPT LOAD), 管道技术可以单独使用EVALSHA来代替EVAL,从而节省带宽(bandwidth)。
-
SCRIPT FLUSH
清空Lua脚本缓存 Flush the Lua scripts cache.
-
SCRIPT KILL
杀死当前正在运行的 Lua 脚本,当且仅当这个脚本没有执行过任何写操作时,这个命令才生效。
这个命令主要用于终止运行时间过长的脚本,比如一个因为 BUG 而发生无限 loop 的脚本,诸如此类。
SCRIPT KILL 执行之后,当前正在运行的脚本会被杀死,执行这个脚本的客户端会从 EVAL 命令的阻塞当中退出,并收到一个错误作为返回值。
另一方面,假如当前正在运行的脚本已经执行过写操作,那么即使执行 SCRIPT KILL ,也无法将它杀死,因为这是违反 Lua 脚本的原子性执行原则的。在这种情况下,唯一可行的办法是使用 SHUTDOWN NOSAVE 命令,通过停止整个 Redis 进程来停止脚本的运行,并防止不完整(half-written)的信息被写入数据库中。
-
SCRIPT LOAD
将脚本 script 添加到脚本缓存中,但并不立即执行该脚本。在脚本被加入到缓存之后,通过 EVALSHA 命令,可以使用脚本的 SHA1 校验和来调用这个脚本。 EVAL 命令也会将脚本添加到脚本缓存中,但是它会立即对输入的脚本进行求值。
脚本可以在缓存中保留无限长的时间(直到执行 SCRIPT FLUSH 为止) 如果给定的脚本已经在缓存里面了,那么不做动作。