Lua脚本编写规范
Lua是一种脚本语言,目的是为了嵌入应用程序中,为应用程序提供灵活的扩展和定制功能。GeminiDB Redis使用的是Lua5.1.5版本,与开源Redis5.0使用的Lua版本是一致的。
使用Lua脚本时,需要经过谨慎的校验,否则可能出现死循环、业务超时等情况,甚至会导致业务不可用。
与开源Redis Lua的区别
- EVAL/EVALSHA命令
命令格式:
EVAL script numkeys key [key …] arg [arg …]
EVALSHA sha1 numkeys key [key …] arg [arg …]
上述命令的语法与操作与开源Redis一致。用户需自己保证将脚本中使用到的Redis key显式的通过key参数传入,而不是直接在脚本中编码。
若使用集群版实例,如果带有多个key参数,则要求所有的key参数必须拥有相同的hash tag。
如果不遵循上述约束,则在Lua中执行涉及这些key的Redis操作时,可能会返回错误信息,甚至可能导致数据的一致性被破坏。
- SCRIPT命令
SCRIPT命令包含了一组管理Lua脚本的子命令,具体可以通过SCRIPT HELP命令查询具体的操作。
SCRIPT大部分命令都与开源Redis兼容,其中需要特别说明的命令如下:
- SCRIPT KILL
GeminiDB Redis是多线程执行的环境,允许同时执行多个Lua脚本,执行SCRIPT KILL,会终止所有正在运行的Lua脚本。
为了方便使用,GeminiDB Redis扩展了SCRIPT KILL命令,用户可以通过‘SCRIPT KILL SHA1’来终止指定哈希值的脚本。若同一时间存在多个节点在执行哈希值相同的脚本,那么这些脚本都会被终止。
另外,由于用户无法设置Lua超时时间(config set lua-time-limit),因此在任意时刻执行SCRIPT KILL都能直接终止脚本,而不是等待脚本超时后才终止。
- SCRIPT DEBUG
目前GeminiDB Redis不支持DEBUG功能,所以该命令执行无效。
- SCRIPT GET
GeminiDB Redis新增命令, 可用来查询通过 "SCRIPT LOAD" 保存到数据库中的脚本内容。
语法为:SCRIPT GET SHA1。
- SCRIPT KILL
- Lua脚本中执行Redis命令
与开源Redis一致,GeminiDB Redis的Lua环境中也提供了一个全局的“redis”表,用于提供各类和Redis Server交互的函数。
如表1为GeminiDB Redis目前支持和不支持的操作列表。
表1 函数列表 支持的操作
不支持的操作
- redis.call()
- redis.pcall()
- redis.sha1hex()
- redis.error_reply()
- redis.status_reply()
- redis.log()
- redis.LOG_DEBUG
- redis.LOG_VERBOSE
- redis.LOG_NOTICE
- redis.LOG_WARNING
- redis.replicate_commands()
- redis.set_repl()
- redis.REPL_NONE
- redis.REPL_AOF
- redis.REPL_SLAVE
- redis.REPL_REPLICA
- redis.REPL_ALL
- redis.breakpoint()
- redis.debug()
- Lua执行环境限制
开源Redis对Lua脚本的执行有一定的限制,比如限制脚本操作全局变量,限制随机函数的结果,限定能够使用的系统库和第三方库等。
GeminiDB Redis也继承了绝大多数的限制,但是针对如下情况,GeminiDB Redis与开源Redis存在差异:
- Write Dirty
开源Redis规定,如果某个脚本已经执行了写操作,那么就不能被SCRIPT KILL停止执行,必须使用SHUTDOWN NOSAVE来直接关闭Redis Server。
GeminiDB Redis不支持执行SHUTDOWN命令,因此这条限制不会被执行,用户仍然可以通过SCRIPT KILL来停止脚本的执行。
- Random Dirty
由于主从复制的原因,开源Redis规定,若脚本执行了带有随机性质的命令(Time, randomkey),则不允许再执行写语义的命令。
例如,如下Lua脚本:
local t = redis.call("time") return redis.call("set", "time", t[1]);
当该脚本的执行传递到从节点时,Time命令获取到的时间一定晚于主节点,因此从节点执行的Set命令的值就会和主节点产生冲突。开源Redis引入了replicate_commands来允许用户决定这种场景下的行为模式。
对于GeminiDB Redis来说,由于没有主从的概念,数据在逻辑上只有一份,因此也就不存在该限制。
- Write Dirty
Lua脚本中禁止执行的命令
Hash:hscan
List:blpop、brpop、brpoplpush
Set:sscan
Sorted Set:bzpopmax、bzpopmin、zscan
Stream:xread、xreadgroup
Generic:rename、renamenx、restore、scan、client、command、config、dbsize、flushall、flushdb、info、keys
Lua:eval、evalsha、script
Pub/Sub:psubscribe、publish、punsubscribe、subscribe、unsubscribe
Transactions:discard、exec、multi、unwatch、watch