文章目录
- Lua脚本
- Lua和Redis数据类型之间的转换
- Lua脚本的原子性
- 错误处理
- 带宽和EVALSHA
- 脚本命令
- 参看文献
Lua脚本
Lua
是一个高效的轻量级脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
Lua
脚本功能是 Reids 2.6
版本的最大亮点, 通过内嵌对Lua
环境的支持, Redis
解决了长久以来不能高效地处理 CAS (check-and-set)
命令的缺点, 并且可以通过组合使用多个命令, 轻松实现以前很难实现或者不能高效实现的模式。
使用脚本的好处
- 减少网络开销,在
Lua
脚本中可以把多个命令放在同一个脚本中运行。 - 原子操作,
Redis
会将整个脚本作为一个整体执行,中间不会被其他命令插入。换句话说,编写脚本的过程中无需担心会出现竞态条件。 - 复用性,客户端发送的脚本会永远存储在
Redis
中,这意味着其他客户端可以复用这一脚本来完成同样的逻辑。
[EVAL] [脚本内容] [key参数的数量] [key …] [arg …]
在 Lua
脚本中使用全局变量 KEYS[i]
来获取第几个key
。在 Lua
脚本中使用全局变量 ARGV[i]
来获取第几个参数。例如:
# 返回结果是一个数组。
> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
在 Lua
脚本中可以调用 Redis
命令。使用如下两个函数来实现:
-
redis.call()
。如果redis.call()
调用命令产生error,redis.call()
将会引发Lua error
并强制EVAL返回1个error 给 命令的调用者。 -
redis.pcall()
。与redis.call()
相似,不同之处在于,会捕获error最后返回代表error的Lua table
。 -
redis.error_reply(error_string)
返回一个error回复。 -
redis.status_reply(status_string)
返回一个status回复。
> eval "return redis.call('set','foo','bar')" 0
OK
> eval "return redis.call('set',KEYS[1],'bar')" 1 foo
OK
如上所示,使用下边的 Lua
脚本而不是上面的那个,key
应当通过 KEYS
全局变量来获取,而不是直接写死在 Lua
脚本中。为什么这样要求呢?
key
通过 KEYS
全局变量显示传递,方便 Redis
分析传递的命令,这在确保 Redis Cluster
可以将请求转发到适当的群集节点上很有用。
Lua和Redis数据类型之间的转换
当 Lua
使用 call()
或 pcall()
调用 Redis
命令时,Redis
返回值将转换为 Lua
数据类型。 同样,在调用 Redis
命令和 Lua
脚本返回值时,Lua
数据类型将转换为 Redis
协议,以便脚本可以控制 EVAL
返回给 client
的值。
Lua
和 Redis
类型之间存在一对一的转换。 下表显示了所有转换规则
Redis 到 Lua
- Redis integer reply -> Lua number
- Redis bulk reply -> Lua string
- Redis multi bulk reply -> Lua table (may have other Redis data types nested)
- Redis status reply -> Lua table with a single ok field containing the status
- Redis error reply -> Lua table with a single err field containing the error
- Redis Nil bulk reply and Nil multi bulk reply -> Lua false boolean type
Lua 到 Redis
- Lua number -> Redis integer reply (the number is converted into an integer)
- Lua string -> Redis bulk reply
- Lua table (array) -> Redis multi bulk reply (truncated to the first nil inside the Lua array if any)
- Lua table with a single ok field -> Redis status reply
- Lua table with a single err field -> Redis error reply
- Lua boolean false -> Redis Nil bulk reply.
- Lua boolean true -> Redis integer reply with value of 1
示例:
> eval "return {1,2,3.3333,somekey='somevalue','foo',nil,'bar'}" 0
1) (integer) 1
2) (integer) 2
3) (integer) 3
4) "foo"
Lua
中没有浮点数,要表示浮点数则用字符串。在 Lua
转换成 Redis
协议过程中遇到 nil
则停止转换。
Lua脚本的原子性
Redis
保证Lua
脚本以原子性方式执行:当一个 Lua
脚本执行时,其他 Lua
脚本或者命令不会被执行, 所有的命令必须等待脚本执行完以后才能执行。从语义上来看,这类似于 Redis
事务中的MULTI
/EXEC
命令。为了防止某个脚本执行时间过程导致 Redis
无法提供服务。Redis
提供了 lua-time-limit
参数限制脚本的最长运行时间。默认是5秒钟。
当脚本运行时间超过这个限制后,Redis
将开始接受其他命令但不会执行(以确保脚本的原子性),而是返回 BUSY
的错误。
错误处理
当调用 redis.call()
导致一个 Redis
命令error时,会停止执行脚本并且返回一个error,表明这个error来自脚本。
> del foo
(integer) 1
> lpush foo a
(integer) 1
> eval "return redis.call('get','foo')" 0
(error) ERR Error running script (call to f_6b1bf486c81ceb7edf3c093f4c48582e38c0e791): ERR Operation against a key holding the wrong kind of value
使用 redis.pcall()
不会引发错误,但是会以上面指定的格式(作为带有 err
字段的 Lua
表)返回错误对象。 该脚本可以通过返回redis.pcall()
返回的错误对象,将确切的错误传递给用户。
带宽和EVALSHA
EVAL
命令强制您一次又一次发送脚本内容。 Redis
不需要每次都重新编译脚本,因为它使用内部缓存机制,在这种情况下使用EVAL
会占用不必要的带宽。为了解决这些问题 Redis
提供了 EVALSHA
。
EVALSHA
的工作原理与 EVAL
完全相同,但是它没有脚本作为第一个参数,而是具有脚本的SHA1摘要
。 该行为如下:
- 如果服务器仍然记住具有匹配的
SHA1摘要
的脚本,则将执行该脚本。 - 如果服务器不记得带有此
SHA1摘要
的脚本,则会返回一个特殊错误,告诉客户端改为使用EVAL
。
> set foo bar
OK
> eval "return redis.call('get','foo')" 0
"bar"
> evalsha 6b1bf486c81ceb7edf3c093f4c48582e38c0e791 0
"bar"
> evalsha ffffffffffffffffffffffffffffffffffffffff 0
(error) `NOSCRIPT` No matching script. Please use [EVAL](/commands/eval).
脚本命令
Redis
有几个脚本命令。
-
SCRIPT FLUSH
。此命令是强制Redis
刷新脚本缓存的唯一方法。 它在可以将同一实例重新分配给其他用户最有用(因为缓存的是其他用户的Lua脚本
,所以要flush
)。 这对于测试客户端库的脚本功能实现也很有用。 -
SCRIPT EXISTS sha1 sha2 ... shaN
。SHA1
摘要的list作为参数,返回0或1的数组,表示为该SHA1
摘要的脚本是否存在。 -
SCRIPT LOAD script
。此命令在Redis脚本缓存中注册指定的脚本。 该命令在我们要确保EVALSHA不会失败,而无需实际执行脚本(因为之前的EVALSHA的脚本都是执行过EVAL之后缓存在脚本系统中的)。 -
SCRIPT KILL
。该命令是中断长时间运行的脚本(已达到配置的脚本最大执行时间)的唯一方法。SCRIPT KILL
命令只能用于在执行期间未修改数据集的脚本(因为停止只读脚本不会违反脚本引擎保证的原子性)。