Redis: 为什么Lua脚本将取代事务操作?

20

事务文档中表示:

"我们可能会废弃和最终删除事务"和"你可以通过脚本完成Redis事务能做的所有事情"

http://redis.io/topics/transactions

但是这真的是这样吗?我看到了一个问题。

在事务中,您可以监视多个变量、读取这些变量,并基于这些变量的唯一状态在调用 EXEC 之前进行完全不同的写入。如果在介入时间内有任何干扰这些变量的状态,EXEC 将不执行事务。(这使您可以重试它。这是一个完美的事务系统。)

然而,EVAL 脚本则不能这样。根据此页面上的文档:

"脚本作为纯函数...无论何时,给定相同的输入数据集,脚本始终以相同的参数评估相同的 Redis 写命令。脚本执行过程中或在不同的脚本执行之间,所进行的操作都不能依赖于任何可能随着脚本执行进展或在脚本执行之间更改的隐藏(非显式)信息或状态,也不能依赖于来自 I/O 设备的任何外部输入。"

http://redis.io/commands/eval

我对 EVAL 的问题在于您无法在脚本内 GET 这些变量的状态,并基于这些变量的状态进行独特的写操作。再次强调:"脚本始终以相同的参数评估相同的 Redis 写命令,给定相同的输入数据集。"因此,结果写入已经确定(从第一次运行缓存),而且 EVAL 脚本不关心脚本内 GET 值是什么。您唯一能做的就是在调用 EVAL 之前对那些变量执行 GET,然后将这些变量传递给 EVAL 脚本,但这里有一个问题:现在您在调用 GET 和调用 EVAL 之间存在原子性问题。

换句话说,在 EVAL 中,您需要获取那些您本来会为事务进行 WATCH 的所有变量,然后将它们传递给 EVAL 脚本。由于脚本的原子性直到实际启动脚本才得到保证,并且在调用 EVAL 启动脚本之前需要获取这些变量,因此在获取变量和将它们传递给 EVAL 之间留下了一个空隙,其中这些变量的状态可能会发生变化。因此,对于一组非常重要的用例,您在使用 WATCH 时拥有的原子性保证在使用 EVAL 时是不存在的。
那么为什么会谈论弃用事务,这将导致重要的 Redis 功能丧失呢?还是说有一种我尚未理解的使用 EVAL 脚本的方法?或者计划推出的功能可以解决 EVAL 的问题?(假设性的例子:如果他们使 WATCH 与 EXEC 以相同的方式工作,则可能有效。)
这个问题有解决方案吗?还是我应该理解 Redis 在长期内可能并非完全安全的事务处理?
3个回答

23

虽然Lua脚本确实可以做任何事务,但我认为Redis事务不会消失。

EVAL脚本不允许您监视变量

当eval脚本正在运行时,没有其他操作可以同时运行。因此,监视变量是没有意义的。一旦在脚本中读取了变量的值,您就可以确定没有其他人修改过这些变量的值。

我看到EVAL的问题在于您无法获取脚本内这些变量的状态,并根据这些变量的状态进行唯一集的写入。

这不是真的。您可以将键传递给eval脚本。在eval脚本中,您可以从Redis中读取值,然后根据这些值有条件地执行其他命令。

脚本仍然是确定性的。如果您将该脚本在从库中运行,它将仍然执行相同的写命令,因为主库和从库具有相同的数据。


如果你所说的基于实际经验,那就太好了!这比我对文档的解释更有说服力:“脚本始终使用相同的参数评估相同输入数据集的Redis写命令。” 对我来说,这意味着正在进行缓存机制,以便脚本第二次甚至不被评估。它只假设在给定相同输入的情况下进行了一组写操作。听起来并不确定。但是,如果您确实有相关经验,我会信任您的专业知识,并感谢您的回答。 - OCDev
1
另外,关于我的假设性例子,我是在谈论一个假设性的功能,其中WATCH可以在EVAL之外和EVAL执行之前使用。(一种新功能,使EVAL在这个意义上的行为类似于EXEC。)这样,GET操作可以在准备将它们传递给EVAL之前执行,并且不需要担心它们会发生变化。(如果在EVAL内部是确定性的话,这就成了无用的争论。)再次感谢。 :) - OCDev
当 Redis 数据库服务器在事务(MULTI/EXEC)和 Eval 之间宕机时会发生什么?假设执行了一些命令,如 POP/PUSH... 并在执行更多命令之前宕机了..? - Kanagavelu Sugumar

19

EVAL脚本实际上扩展并简化了事务的功能。

可以将Redis中的两种事务视为以下方式:

1.过程式(MULTI EXEC)

Pure MULTI EXEC是将命令分组一起执行,并返回每个命令的响应数组。它有严重的局限性。它不允许事务内使用一个命令的中间结果在下一个命令中使用。在这种纯粹的形式下,在实际应用中它并不是非常有用。它基本上是具有原子性保证的流水线处理。

在事务之前添加WATCH key,允许乐观锁定并在事务中使用从Redis外部获取的值。如果发生竞争条件,则事务失败并必须重试。这使应用程序逻辑变得复杂,而且这种乐观主义经常是不合理的,因为您可能会陷入无尽的重试循环。

2.函数式(EVAL脚本)

EVAL不仅将Redis命令分组,还提供了完整编程语言的功能,特别是条件、循环和本地变量。在Lua脚本中,您可以使用一个命令中的Redis中的值并在下一个命令中使用它们。

您提交脚本,以原子方式执行。这是由单线程,“停止世界”的方法保证的。在执行脚本时,不会执行任何其他脚本或Redis命令。因此,EVAL也有一个限制:脚本必须小而快,以防止阻塞其他客户端。

我们还需要相信其他客户端不会提交慢脚本,因为这应该被视为编程错误。它非常适合安全模型,因为“Redis旨在由受信任的客户端在受信任的环境内访问”


当 Redis 数据库服务器在事务(MULTI/EXEC)和 Eval 之间宕机时会发生什么?假设执行了一些命令,如 POP/PUSH...,并在执行几个更多的命令之前宕机了...? - Kanagavelu Sugumar

0

EVAL脚本可以完成所有交易脚本的功能,但在单线程环境中乐观锁定并不是很有用。

脚本非常有用的一点是条件执行,例如执行某些操作,然后根据结果选择不同的后续执行。这只能通过脚本来完成,这使得脚本在构建需要条件检查的功能方面更加强大,而这种情况经常发生。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接