dot Redis 8 来了——而且是开源的

了解更多

11.1.1 将 Lua 脚本加载到 Redis 中

返回主页

11.1.1 将 Lua 脚本加载到 Redis 中

一些旧的(并且仍然使用的)用于 Redis 2.6 的 Python Redis 库尚未提供
直接加载或执行 Lua 脚本的功能,因此我们将花一些时间来创建
脚本的加载器。 要将脚本加载到 Redis 中,有一个由两部分组成的命令
叫做 SCRIPT LOAD,当提供一个字符串(一个 Lua 脚本)时,它将存储
该脚本以供以后执行,并返回该脚本的 SHA1 哈希值。 稍后,当我们想要
执行该脚本时,我们运行 Redis 命令 EVALSHA 以及 Redis 返回的哈希值,
以及脚本需要的任何参数。

我们执行这些操作的代码将受到当前 Python Redis 的启发
代码。 (我们主要使用我们的方法,因为它允许使用我们想要的任何连接,而无需
必须显式创建新的脚本对象,这在处理服务器分片时可能很有用。) 当我们将一个字符串传递给我们的 script_load()
(在处理服务器分片时。)当我们传递一个字符串给我们的 script_load()
函数时,它将创建一个函数,稍后可以调用该函数以在 Redis 中执行该脚本。
调用对象以执行脚本时,我们必须提供 Redis 连接,
然后它将在第一次调用时调用 SCRIPT LOAD ,然后在以后所有的
调用时调用 EVALSHAscript_load() 函数如下面的列表所示。

列表 11.1一个加载脚本以便以后调用的函数
def script_load(script):
    sha = [None]

将 SCRIPT LOAD 的结果的缓存 SHA1 哈希值存储在一个列表中,以便我们以后可以从 call() 函数中更改它。

    def call(conn, keys=[], args=[], force_eval=False):

调用加载的脚本时,我们必须提供一个连接、脚本将操作的一组键以及该函数的任何其他参数。

            if not force_eval:
                if not sha[0]:

只有在 SHA1 哈希未被缓存时,我们才会尝试加载脚本。

                        sha[0] = conn.execute_command(
                            "SCRIPT", "LOAD", script, parse="LOAD")

                try:
                            return conn.execute_command(
                               "EVALSHA", sha[0], len(keys), *(keys+args))

从缓存的 SHA1 执行命令。

                        except redis.exceptions.ResponseError as msg:
                            if not msg.args[0].startswith("NOSCRIPT"):
                                raise

如果错误与缺少脚本无关,请再次引发异常。
如果我们收到与脚本相关的错误,或者我们需要强制执行脚本,请直接执行该脚本,这将在完成后自动将脚本缓存在服务器上(使用与我们已缓存的相同的 SHA1)。

                return conn.execute_command(
                    "EVAL", script, len(keys), *(keys+args))
            return call

返回在调用时自动加载和执行脚本的函数。

您会注意到,除了我们的 SCRIPT LOADEVALSHA 调用之外,我们还捕获了一个
如果我们已在本地缓存脚本的 SHA1 哈希值,但服务器
不知道它,则可能发生的异常。 如果服务器重新启动、如果有人
已执行 SCRIPT FLUSH 命令以清除脚本缓存,或者如果我们在不同的时间向我们的函数提供了
与两个不同的 Redis 服务器的连接,则可能会发生这种情况。 如果
我们发现脚本丢失了,我们使用 EVAL 直接执行该脚本,该脚本
除了执行脚本外,还缓存该脚本。 此外,我们允许客户端直接执行
脚本,这在执行 Lua 脚本作为事务的一部分或其他流水线序列时很有用。
其他流水线序列。

LUA 脚本的键和参数埋在我们的脚本加载器中,您
可能已经注意到,在 Lua 中调用脚本需要三个参数。 第一个
是一个 Redis 连接,现在应该是标准连接。 第二个参数
是一个键的列表。 第三个是函数的参数列表。

键和参数之间的区别在于您应该传递
脚本将读取或写入的所有键作为键
参数的一部分。 这是为了可能让其他层验证所有键是否
在同一个分片上,如果您使用的是多服务器分片技术,例如
第 10 章中描述的技术。

当 Redis 集群发布时,它将提供自动多服务器分片,
将在运行脚本之前检查键,如果任何
访问了不在同一服务器上的键,将返回一个错误。

第二个参数列表没有这样的限制,旨在保存数据
以便在 Lua 调用中使用。

让我们在控制台中尝试一个简单的示例来开始。

>>> ret_1 = script_load("return 1")

大多数用途将加载脚本并存储对返回函数的引用。

>>> ret_1(conn)

然后,我们可以通过传递连接对象和任何所需的参数来调用该函数。

1L

结果将被返回并转换为适当的 Python 类型(如果可能)。

正如您在本例中看到的,我们创建了一个简单的脚本,其唯一目的是
返回一个值为 1。当我们使用连接对象调用它时,脚本被加载
然后执行,导致返回值 1。

从 LUA 返回非字符串和非整数值

由于 Lua 允许数据传入和传出的方式存在限制,因此某些数据类型
Lua 中可用的数据类型不允许传出,或者在返回之前被更改。
表 11.1 显示了此数据在返回时如何更改。

表 11.1从 Lua 返回的值及其翻译成的值

Lua 值

转换为 Python 期间会发生什么

true

变成 1

false

变成 None

nil

不会变成任何东西,并阻止表格中剩余的值被返回

1.5(或任何其他浮点数)

小数部分被丢弃,将其转换为整数

1e30(或任何其他大浮点数)

转换为 Python 版本的最小整数

"strings"

未更改

1(或任何其他整数 +/-253-1)

整数不变地返回

由于返回各种数据类型时会导致歧义,因此您
应尽最大努力尽可能显式地返回字符串,并执行任何
手动解析。 我们将仅返回布尔值、字符串、整数和 Lua 表
(它们被转换为 Python 列表)用于我们的示例。

现在我们可以加载和执行脚本了,让我们从一个简单的例子开始
第 8 章,创建一个状态消息。