【redis】lua脚本入门
之前文章中提到过,redis官方在考虑lua代替事务,与事务相比,lua脚本也具有原子性,并且lua脚本在执行的时候,也会阻塞其他脚本或者redis命令并发执行,所以要尽量避免执行时间很长的脚本。
另外还要注意之前提到过的lua脚本导致内存泄漏问题,如果一组命令中的第一个就导致内存超出,那么这段lua脚本不会运行,但是第二个及之后的命令导致内存超出,redis是不会报错停止的,它会将整段脚本执行完(这也是其原子性的体现)
在Lua脚本执行期间,不执行任何 key 过期操作。当一个Lua脚本运行时,从概念上讲,master 中的时间是被冻结的,这样脚本运行的时候,一个给定的键要么存在要么不存在。这可以防止 key 在脚本中间过期,保证将相同的脚本发送到 slave ,从而在二者的数据集中产生相同的效果。
与事务只在所有语句执行完才能看到返回信息不同,lua可以在运行途中输出执行结果,好根据此结果判断接下来要干啥(if),还支持客户端缓存lua语句,这可以节省带宽,加快执行速度。
lua脚本可以嵌入多种语言,体积小巧,在php中可以作为一个扩展安装运行:https://www.php.net/manual/zh/book.lua.php (用户笔记0和1介绍php7以上版本安装lua2.0.7) https://blog.csdn.net/weixin_29504987/article/details/115501223
纯函数
脚本应该被写成纯函数,对于同样的数据集输入,给定相同的参数,脚本执行的 redis 写命令总是相同的。脚本执行的操作不能依赖于任何隐藏(非显式)数据,不能依赖于脚本在执行过程中、或脚本在不同执行时期之间可能变更的状态,并且它也不能依赖于任何来自 I/O 设备的外部输入。
-
Lua 没有访问系统时间或者其他内部状态的命令。
-
在执行随机命令之后(比如randomkey、srandmember key [count]、time),接着执行可以修改数据集的 Redis 命令,Redis 会返回一个错误。如果脚本只是执行只读操作,那么就没有这一限制。注意,随机命令并不一定就指那些带 rand 字眼的命令,任何带有非确定性的命令都会被认为是随机命令,比如time。
-
对无序返回默认(slient)进行字典排序。比如在 Redis 命令行客户端中直接执行smembers key,返回的元素是无序的,但是,假如在脚本中执行
redis.call("smembers", KEYS[1]),那么返回的总是排过序的元素。 -
对 Lua 的伪随机数生成函数
math.random和math.randomseed进行修改,每次运行脚本时,math.random产生的随机数序列总是相同的。(math.randomseed可以为脚本添加一个额外的参数,让这个参数作为 Lua 的随机数生成器的 seed 值,这样的话,只要给脚本传入不同的 seed ,脚本就会生成不同的列表元素。(redis 实现保证math.random和math.randomseed的输出和运行 Redis 的系统架构无关,无论是 32 位还是 64 位系统,无论是小端(little endian)还是大端(big endian)系统,这两个函数的输出总是相同的)。
全局变量保护
为了防止不必要的数据泄漏进 Lua 环境, redis 脚本不允许创建全局变量。如果一个脚本需要在多次执行之间维持某种状态,它应该使用 redis key 来进行状态保存。
一旦用户在脚本中混入了 Lua 全局状态,那么 AOF 持久化和复制(replication)都会无法保证,可以将脚本中用到的所有变量都使用 local 关键字定义为局部变量。
例1:

运行
eval、evalsha
lua脚本运行主要有两种方式,eval和evalsha。
eval:直接运行lua脚本
evalsha:根据给定的sha1校验码,对缓存在服务器中的脚本进行求值。redis包含一组将脚本写入缓存、清除、判断是否存在的script命令,也包含杀死正在运行脚本的命令。
虽然redis不会每次重新编译脚本,但是用evalsha可以减少脚本数据传递,减轻带宽压力。
redis.call()、redis.pcall()
执行redis命令的函数有两种:
redis.call():在运行中出错时停止运行并返回错误信息
redis.pcall():出错时并不引发(raise)错误,而是返回一个带 err 域的 Lua 表(table),用于表示错误
可以在命令行直接运行简单脚本:
1是指一个键名参数KEYS,之后的是附加参数ARGV。
eval "return redis.call('set',KEYS[1],ARGV[1])" 1 name ronaldo

可以执行lua脚本文件
例2:test.lua
--[[
多行注释
--]]
local mykey = KEYS[1]
local mykey2 = KEYS[2]
local myvalue = ARGV[1]
local myvalue2 = ARGV[2]
return {mykey,mykey2,myvalue,myvalue2}
执行:因为本地运行,redis-cli --eval 脚本路径/test.lua -h -p -a都是默认可以省略。之后的参数中,‘,’之前的叫KEY,可以由KEY[1],KEY[2]...获取,‘,’之后的叫ARGV,由ARGV[1]...获取。注意‘,’要前后有空格

只有一个return,需要返回多个值需要包含在{}中(这实际上是lua的table数据类型,是一个关联数组),可以自定义格式,例2中的返回可以改为
return {mykey,{myvalue},mykey2,{myvalue2}}

例3:
test.lua
redis.call('set','name',ARGV[1])
local myname = redis.call('get','name')
if myname == 'kaka' then
return 'just you'
else
return 'sorry,not you'
end
执行:
redis-cli --eval 脚本路径/test.lua -h -p -a KEY[1] KEY[2]... , ARGV[1] ARGV[2] ...
本例中未用到KEY,‘,’前空格即可,直接写AGRV参数

更多语法参考:https://www.runoob.com/lua/lua-basic-syntax.html 需要注意执行redis语句的lua脚本的限制
还要注意集群分区问题
沙箱(sandbox)和最大执行时间
脚本应该仅仅用于传递参数和对 Redis 数据进行处理,它不应该尝试去访问外部系统(比如文件系统),或者执行任何系统调用。
除此之外,脚本还有一个最大执行时间限制,它的默认值是 5 秒钟,一般正常运作的脚本通常可以在几分之几毫秒之内完成,这个限制主要是为了防止因编程错误而造成的无限循环而设置的。
当一个脚本达到最大执行时间的时候,为了保证其原子性,它并不会自动被 Redis 结束,当脚本超时:
-
Redis 记录一个脚本正在超时运行
-
Redis 开始重新接受其他客户端的命令请求,但是只有script kill和shutdown nosave两个命令会被处理,对于其他命令请求, Redis 服务器只是简单地返回
BUSY错误。 -
可以使用 script kill命令将一个仅执行只读命令的脚本杀死,因为不修改数据,因此杀死这个脚本并不破坏数据的完整性
-
如果脚本已经执行过写命令,那么唯一允许执行的操作就是shutdown nosave ,它通过停止服务器来阻止当前数据集写入磁盘
流水线(pipeline)上下文(context)中的evalsha
之前我们提到过,redis事务底层实际上是使用pipeline以提高性能。
在流水线中,必须保证命令的执行顺序。一旦在流水线中因为 EVALSHA 命令而发生 NOSCRIPT 错误,那么这个流水线就再也没有办法重新执行了,否则的话,命令的执行顺序就会被打乱。
为了防止出现以上所说的问题,客户端库实现应该实施以下的其中一项措施:
-
总是在流水线中使用eval命令
-
检查流水线中要用到的所有命令,找到其中的eval命令,并使用 SCRIPT EXISTS sha1 [sha1 …] 命令检查要用到的脚本是不是全都已经保存在缓存里面了。如果所需的全部脚本都可以在缓存里找到,那么就可以放心地将所有 EVAL 命令改成 EVALSHA 命令,否则的话,就要在流水线的顶端(top)将缺少的脚本用 SCRIPT LOAD script 命令加上去。
lua语法入门
八大数据类型
nil
无效值、空值,在表达式中相当于false,对于全局变量和 table,nil 还有一个"删除"作用,给全局变量或者 table 表里的变量赋一个 nil 值,等同于把它们删掉,遍历时可以发现nil值被删除了。
作比较时应加上“”:type(X)=="nil" true,type(X)==nil false。因为type()函数本身返回的是一个string。
boolean
false 和 nil 看作是 false,其他的都为 true,数字0也是true。
local a = 0
if a then
return '0 is true'
else
return '0 is not true'
end
![]()
number
默认只有double型
string
单双引号引用,和用[[ ]]引用字符串块
对一个数字字符串上进行算术操作时,Lua 会尝试将这个数字字符串转成一个数字
#string 计算string长度,返回数字,中文根据编码长度不同
table
table是一个"关联数组",索引可以是数字或者是字符串,用它可以实现数组,哈希表,集合,对象。
默认初始索引一般以 1 开始(这就是之前例子中的KEY[1])
local a = {}
for i = 1,10 do
a[i] = i
end
return a

function(函数)
函数可以存在变量里,函数也可以看做一个值。
通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等
thread(线程)
在 Lua 里,最主要的线程是协同程序(coroutine)。它跟线程(thread)差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。
线程跟协程的区别:线程可以同时多个运行,而协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起(suspend)时才会暂停。
userdata(自定义类型)
userdata是用户自定义类型,用于将应用、c、c++等语言创建的类型保存在userdata里,如指针等。
参考:
更多推荐

所有评论(0)