更新时间:2024-05-30 GMT+08:00

Lua脚本编写规范

Lua是一种脚本语言,目的是为了嵌入应用程序中,为应用程序提供灵活的扩展和定制功能。GeminiDB Redis使用的是Lua5.1.5版本,与开源Redis5.0使用的Lua版本是一致的。

使用Lua脚本时,需要经过谨慎的校验,否则可能出现死循环、业务超时等情况,甚至会导致业务不可用。

与开源Redis Lua的区别

  1. 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操作时,可能会返回错误信息,甚至可能导致数据的一致性被破坏。

  2. 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。

  3. Lua脚本中执行Redis命令

    与开源Redis一致,GeminiDB Redis的Lua环境中也提供了一个全局的“redis”表,用于提供各类和Redis Server交互的函数。

    表1GeminiDB 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()
  4. 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来说,由于没有主从的概念,数据在逻辑上只有一份,因此也就不存在该限制。

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