初识Redis lua脚本

Lua脚本

Lua是一个高效的轻量级脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。

Lua 脚本功能是 Reids 2.6 版本的最大亮点, 通过内嵌对Lua环境的支持, Redis 解决了长久以来不能高效地处理 CAS (check-and-set) 命令的缺点, 并且可以通过组合使用多个命令, 轻松实现以前很难实现或者不能高效实现的模式。

使用脚本的好处

  1. 减少网络开销,在Lua脚本中可以把多个命令放在同一个脚本中运行。
  2. 原子操作,Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。换句话说,编写脚本的过程中无需担心会出现竞态条件。
  3. 复用性,客户端发送的脚本会永远存储在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 的值。

LuaRedis 类型之间存在一对一的转换。 下表显示了所有转换规则

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 ... shaNSHA1摘要的list作为参数,返回0或1的数组,表示为该SHA1摘要的脚本是否存在。
  • SCRIPT LOAD script。此命令在Redis脚本缓存中注册指定的脚本。 该命令在我们要确保EVALSHA不会失败,而无需实际执行脚本(因为之前的EVALSHA的脚本都是执行过EVAL之后缓存在脚本系统中的)。
  • SCRIPT KILL。该命令是中断长时间运行的脚本(已达到配置的脚本最大执行时间)的唯一方法。 SCRIPT KILL命令只能用于在执行期间未修改数据集的脚本(因为停止只读脚本不会违反脚本引擎保证的原子性)。

其他 Lua脚本的资料请见 Redis官方文档 Lua脚本

参看文献

[1] Redis官方文档 Lua脚本
[2] Redis使用之Lua脚本

猜你喜欢

转载自blog.csdn.net/besmarterbestronger/article/details/106982194