Lua脚本使用规范与注意事项
云数据库Redis 支持 Lua 相关命令,通过 Lua 可轻松实现更复杂的业务逻辑,并原子性地执行。本文介绍通过 Redis 使用 Lua 脚本的使用规范和注意事项。
Lua 相关命令使用方法
eval eval 命令执行给定的脚本,其语法规则如下:
1.EVAL script numkeys [key [key ...]] [arg [arg ...]]
- script 为需要执行的脚本
- numkeys 为传入脚本的 key 的数量,在 lua 脚本中可以使用 KEYS这个预定义的全局变量获取到传入的 keys
- [key [key ...]] 为传入的 key,这里 key 的数量需要和 numkeys 保持一致
- [arg [arg ...]] 为传入 lua 脚本的参数
下面是在 Python 中使用 eval 命令执行 lua 脚本的例子:
1. import redis
2. lua = """
3. if redis.call("type", KEYS[1]) == 'string' and redis.call("GET", KEYS[1]) == ARGV[1]
4. then
5. redis.call("SET",KEYS[1], ARGV[2])
6. return 1
7. end
8. return 0
9. """
10. conn = redis.Redis("127.0.0.1", 6379)
11. conn.eval(lua, 1, "foo", "a", "b")
上面的例子中,实现 CAS(compare and set)功能,当 KEYS[1]的值等于 ARGV[1]时,将其此 key 的值设置为 ARGV[2],如果成功执行了设置,则返回 1,否则返回 0。
eval_ro
eval_ro 命令的语法和 eval 一样,区别在于 eval_ro 表明将要执行的 lua 脚本不会修改 redis 中的数据,其中只会执行读操作(read only)。
1.EVAL_RO script numkeys [key [key ...]] [arg [arg ...]]
如果确定 lua 脚本中只会执行读操作,那么可以使用 eval_ro 来执行它,这样使用云数据库Redis 集群时,可能将其发送至从节点执行,以降低主节点的压力。
script load
当 redis 执行过一个 lua 脚本后,会将此脚本缓存下来,当用户需要再次执行此 lua 脚本时,可以不必再次提供此脚本。可以使用 script load 命令将 lua 脚本载入到 redis 节点中,后续可以使用一个表示该脚本的 id 来执行此脚本。
在云数据库Redis 集群版中,执行 script load 后,会将此 lua 脚本载入到所有的分片中,只要有一个分片载入失败,script load 命令就会失败。
下面是在 Python 中使用 script load的例子:
1. import redis
2. lua = "..."
3. conn = redis.Redis("127.0.0.1", 6379)
4. sha = conn.script_load(lua)
5. assert sha == "2fcf32663b5faac34faebe59525c14b035c12705"
载入 lua 脚本时,redis 会使用 sha1 算法对该 lua 脚本执行 hash 操作,得到一个字符串,后续可以使用此字符串来指代此 lua 脚本。
evalsha
使用 script load载入脚本后, 可以得到一个表示该 lua 脚本的 sha 值,而后可以使用 evalsha 来执行此 lua 脚本。evalsha 的语法如下:
EVALSHA sha1 numkeys [key [key ...]] [arg [arg ...]]
和 eval 唯一的不同是,这里使用 sha1 值指代此前载入的 lua 脚本。
evalsha_ro
和 eval_ro 类似,evalsha 也有其只读版本 evalsha_ro。
script exists
给定一个(或多个)脚本的 SHA1,script exists返回每个 SHA1 对应的脚本是否已缓存在当前 Redis 节点中。若脚本已存在,则返回1,否则返回 0。 在云数据库Redis 集群版中,script exists 会被转发给所有分片,一旦有任何一个分片中不存在提供的脚本,就会返回不存在。
script flush
清空所有分片中缓存的 lua 脚本。
script kill
停止正在运行的 Lua 脚本。
lua脚本使用限制
至少需要提供一个 key
集群版中,Proxy 需要有 key 的信息才能知道将该 lua 脚本转发至哪一个分片中,因此在执行 EVAL 和 EVALSHA 命令时,必须提供至少一个 key,即 numkeys 不能为 0。
1.EVAL script numkeys [key [key ...]] [arg [arg ...]]
比如像下面这样的命令是不支持的:
1.eval 'return redis.call("GET", "ABC")' 0
若传入多个 key,需保证其落在同一个 slot 上
eval 支持传递多个 key,lua 脚本中涉及对这多个 key 的操作。如果传入了多个 key,则要保证这些 key 落在同一个 slot 中。因为 lua 脚本最终会在某个分片的 redis 节点中执行,如果其中涉及的多个 key 不再同一个 slot 上,那么多个 key 对应的数据可能在不同的节点中,这样自然是不符合预期的。 在 云数据库Redis 集群版中,Proxy 会检查 numkeys 的值是否为 0,若为 0,则 eval/evalsha/eval_ro/evalsha_ro 命令会失败。 如果 LUA 脚本中涉及到对多个 key 的操作,可以考虑对业务做适当改造,使用 hashtag 保证这多个 key 落在同一个 slot 上:
eval "return {KEYS[1], KEYS[2]}" 2 user_1234 user_2345 # NOT OK
eval "return {KEYS[1], KEYS[2]}" 2 {user}_1234 {user}_2345 # OK
不要在 lua 脚本中对 key 硬编码
所有的 key 均应该通过参数传递,不应该在 lua 中硬编码 key,虽然这样不会报错,但该 key 会被写入本不属于它的分片中。
eval 'return redis.call("GET", "ABC")' 0 # NOT OK
eval 'return redis.call("GET", KEYS[1])' 1 ABC # OK
常见报错
错误信息 | 原因 |
---|---|
wrong number of arguments for 'eval' command | eval 命令没有正确地提供一个以上的 key |
No matching script. Please use EVAL. | 当前执行的 lua 脚本不存在,需要下您执行载入 |