点 闪电般的未来将来到你所在城市的活动中。

加入我们,参加 Redis Released

Lua 助手

返回术语表

Redis 可使用 redis-cli 做一些精彩之事,同时在 Redis 和你选择的语言之间做的事更多。但有时,使用客户端/服务器架构无法高效或安全地实现一些行为,则需要在数据库层运行逻辑。这是 Lua 发挥作用的地方。Lua 作为一种脚本语言被编译到 Redis 中。借助 Lua,你可以以原子方式在 Redis 中执行代码,而不会给客户端造成传输开销。

此类任务的一个示例是向哈希字段添加一个值。虽然 Redis 可以轻松地使用 APPEND 向字符串键追加一个值,但不存在此类命令来向哈希字段追加一个值。虽然你可以尝试通过使用客户端获取字段值、向该值追加新的字符串并重置哈希字段来实现此目的,但这是一个糟糕的主意。由于这不是一个原子操作,在你向哈希字段追加值的期间,有另外一个客户端可能突然出现并更改它,然后,原始客户端将覆盖新的更新。

客户端 1客户端 2
1>HGET myhash myfield “hello”
2[客户端向 “hello” 追加 “ world”]>HSET myhash myfield goodbye 0
3> HSET myhash myfield “hello world” 0
4> HGET myhash myfield “hello world”

如第 2 行所示,“goodbye” 的更新将会丢失。我们可以使用一个 Lua 帮助程序来解决此问题,并消除将值移动到客户端时产生的发送/接收开销。

在任意文本编辑器中,我们需要创建一个 Lua 脚本。我们将其命名为“happend.lua”

local original = redis.call('HGET',KEYS[1],ARGV[1])
return redis.call('HSET',KEYS[1], ARGV[1], original .. ARGV[2])

在第一行,我们创建一个名为 original 的局部变量,它将存储哈希键的当前值,该键存储在第一个传递的键中,并且该字段是第一个非键参数。理解这一点很重要,即在执行时 Lua 脚本会在键和非键参数之间进行区分。

在第二行,我们在同一个键和字段上调用 HSET,然后将原始值连接到第二个非键参数。这是将其返回到 Redis 中,因此,我们保留 HSET 的原始返回值。

虽然你可以通过 EVAL 命令直接执行 Lua 脚本,但这会相当杂乱且低效。Redis 有一个内置的脚本缓存,它允许预加载脚本,然后通过脚本的 SHA1 哈希摘要引用它。要从命令行加载脚本,我们将使用 cat 和 redis-cli。

$ redis-cli -a yourRedisPassword SCRIPT LOAD "$(cat ./happend.lua)"
"d30c7f6d0c23fcfe6d4630b11f4e3db4cb2db099"

(请注意:如果你的脚本即使不同一个字符,你也会获得一个完全不同的哈希字符串)

现在,在 redis-cli 中我们可以使用 EVALSHA 来调用我们的脚本并添加

> HSET mynewhash greeting "Hello"
(integer) 1
> EVALSHA d30c7f6d0c23fcfe6d4630b11f4e3db4cb2db099 1 mynewhash greeting " world"
(integer) 0
> HGET mynewhash greeting
"Hello world"

让我们分析一下 EVALSHA 命令。第一个参数是我们使用 SCRIPT LOAD 创建的脚本的 SHA1 摘要。第二个参数是键的数目。在本例中,我们正在处理一个键,因此,它为 1。第三个参数是我们正在处理的键。第三个参数是我们希望处理的字段,最后,第四个参数是我们正在追加到该字段的值。

由于附加是在 Lua 脚本内部进行的,因此,我们刚才的场景不会发生,因为 Lua 脚本是以完全同步和原子的方式执行的。

虽然 Lua 可以是极为有用的问题解决者,但在使用时需谨慎对待。脚本确实会阻塞服务器并且可能导致数据库无响应。在分片情况下,脚本会尝试将所有操作都保存到一台服务器以避免跨分片错误。