Redis中的Lua何时真正使用?

19

我已开始研究并尝试了一下Lua,发现它非常适合用于获取键的范围。例如:

business:5:visits:2013-11-12
business:5:visits:2013-11-13
etc

使用Lua,我只需要向Redis发送一个命令而不是整个日期范围。

现在我正在考虑将更多的逻辑转换并移至Redis。

以我们当前的消息存储过程为例:

// create a new unique id
redisClient.incr(Config.messageId, function(err, reply) {
    var messageId = reply.toString();
    var timestmp = Date.now();

    // push message
    redisClient.zadd(Config.history + ':' + obj.uid + ':' + obj.channel.replace(/\s+/g, ''), timestmp, messageId);

    // store the message data by messageId
    redisClient.hmset(Config.messageHash + ':' + messageId, {
        'user_id': obj.uid,
        'text_body': "some text",
        'text_date': timestmp,
    });


    // set expires
    redisClient.expire(Config.history + ':' + obj.uid + ':' + obj.channel.replace(/\s+/g, ''), Config.messageExpire);
    redisClient.expire(Config.messageHash + ':' + messageId, Config.messageExpire);


    // add to mysql-sync queue
    redisClient.RPUSH(Config.messageMySQLSyncQueue, Config.messageHash + ':' + messageId);

});

上述内容可以很容易地转换成Lua,但为了性能是否值得呢?

如果用Lua编写这个程序,只需要向Redis发出1条命令,会更快吗?这会导致阻塞其他命令吗?

5个回答

22

Lua脚本的作用类似于MULTI命令。实际上,您可以使用Lua实现大多数使用Redis客户端开发的MULTI命令。也就是说,您可以将某些复杂操作封装到一个脚本中,您的数据层将执行原子的写入操作而无需担心在Redis上的数据建模策略。

此外,当您想要执行快速但复杂的读取操作时,我发现它们非常有用。例如,您可能想要按顺序获取对象。对象存储在散列键中,而顺序由排序集合键定义。您获取所谓的排序集合的范围,并使用hmget在散列中获取对象。

最重要的一点是Lua脚本应该实现尽可能快地执行,因为Redis在运行Lua脚本时会阻塞其他操作。也就是说,您需要进行快速中断,否则您的整体Redis性能将大幅下降。

不使用Lua的理由

我认为您只有在真正需要时才应该使用它们。通常,客户端使用高级编程语言(如C#、Java、JavaScript、Ruby ...)进行开发,并提供更好的开发体验:良好的调试器、IDE、代码完成等。

总结:如果您可以证明将领域逻辑的某些部分转换为Redis Lua脚本会带来实际的好处(在性能方面),则应该使用它们。


Redis在运行Lua脚本时如何阻止所有其他操作?例如,如果我有10个应用程序连接到同一个Redis集群,因为我正在运行Lua脚本,其他9个应用程序会受到影响吗? - Fernando
1
Lua脚本的读/写操作是否都是如此? - Fernando
@ItamarHaber 嘿! - Matías Fidemraizer

9
Redis的Lua脚本有一个缺点:即使它们在同一台物理服务器上,您也无法向另一个Redis实例发出操作。在一台服务器上运行多个Redis实例以利用所有CPU核心是一种常见做法。所以您需要从客户端控制Redis的许多实例。
总结以上内容:
1. Lua脚本是纯函数。您不能进行外部请求,如http请求或甚至对其他Redis实例的请求。您不能产生任何副作用。
2. Lua脚本会阻塞一切。您不能同时运行两个Lua脚本。因此,您不能在Lua中执行任何大型任务,例如无限循环以在后台执行某些操作。这就像在Windows 95上格式化软盘(如果您知道我的意思):-)
简而言之,Redis不是应用程序服务器。因此,您无法轻松地在Lua上编写任何逻辑,并确保一切都正确。
如果您需要在NoSQL数据库中使用真正的应用程序服务器,则可以尝试Tarantool。它也具有Lua脚本,但区别在于它们不会相互阻塞,它们仍将作为一个ACID事务执行(如果您不进行外部请求),并且它们可以执行您想要的任何副作用,甚至发出对Tarantool或Redis等外部数据库的请求或执行任何http请求。这是通过在单独的纤程中执行每个Lua脚本以及对由Lua脚本完成的所有更改进行基于行的复制来实现的。

4
我在我的MAC上启动了2个Redis实例,对其中一个实例运行了一个会一直等待的Lua脚本,对另一个实例运行了一个不同的Lua脚本,它返回了结果。Ping命令也正常返回。但是这个答案是错误和误导性的。 - sumanth232
1
同意,如果这两个进程是独立的并且没有共享任何东西,据我所知,这听起来像是一个非常错误的陈述。 - jd_

7

TL;DR:不要使用 Lua 脚本(来完成此任务)

稍长一些:Redis 的 Lua 脚本语义会调用代码生成键名,并声明脚本所使用的所有键都应该作为参数提供(使用KEYS 数组)。

还需要说明的更详细:请参见来自http://redis.io/commands/eval的引用:

在执行 Redis 命令之前必须对其进行分析,以确定命令将操作哪些键。为了使 EVAL 命令符合这个规则,必须显式地传递键。这在很多方面都是有用的,特别是为了确保 Redis 集群能够将您的请求转发到适当的集群节点。


7
Lua很不错,我们在某些情况下使用它 - 特别是当我们想要进行一些原子操作时,但在您的情况下,将您上面的代码转换为Lua脚本并在Redis中运行会遇到问题。这是因为这一行代码:
var timestmp = Date.now();

在这样的一条命令之后,你不能再执行SET操作了,这基本上是因为Redis如何处理主从复制。请参考这里:http://redis.io/commands/eval#scripts-as-pure-functions
这就像是反对使用Lua脚本的又一个论点。

6
Lua脚本非常强大。正如您正确描述的那样,它允许在Redis服务器和客户端之间限制网络往返次数。此外,您不需要一直将脚本作为字符串发送,只需在第一次调用后发送SHA1即可,这非常小。
有关Lua的最佳实践:如果您将分片数据或使用复制,请务必将“只读”Lua脚本与实际写入集群的脚本分开(必须在主节点上执行)。
在调用lua之前计算出redis中所需的所有密钥,还要注意,在lua中不能原生访问与时间相关的变量(嵌入在redis中),这意味着时间相关的值必须在lua之外计算。通常最好在lua之外完成大部分工作,仅使用它来批处理操作以针对redis并限制网络活动。
最后,请务必小心lua超时(可以在redis中覆盖它)。由于lua执行是在redis实例中阻塞的,因此它不能太长时间执行(以“分而治之”的方式设计算法),否则所有操作都会变慢。
此外,可能会出现阈值问题:请考虑在lua脚本中清除redis键。如果需要处理的键/操作过多,则由于lua超时而失败。然后,由于活动,您的数据在redis中增长。下次尝试使用lua清除时,它将不得不处理更多的键以清除redis,结果成功的机会更小!您可能会因为lua超时而出现内存不足...

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