Redis Lua脚本 math.random

4

我刚刚发现了Redis中Lua环境的一个有趣行为:

我有一个Lua脚本执行一些简单的集合操作,并在脚本末尾生成一个唯一的时间戳ID,以便将Redis用作时间戳Oracle - 就像这样:

...
local time = redis.call('TIME')
local millis = (tonumber(time[1]) * 1000) + math.floor(tonumber(time[2]) / 1000)
local version = string.format("%.0f",mills) .. string.format("%05d", math.random(99999))

现在的version看起来是这样的:145209287564117083,由时间戳和五个随机数字组成 - 至少我是这么认为的。但事实上,最后的五个随机数字(由math.random(99999)生成)根本不是随机的,而是始终是数字17083,无论脚本执行多少次。对我来说这并不是个大问题(因为我可以在脚本返回后添加随机数字),但我没料到会出现这种情况,因此需要花费一些时间来寻找错误。希望这些信息能够节省一些时间。

尝试在脚本顶部加入math.randomseed(os.time()) - warspyking
1
@warspyking - 这不会起作用,因为Redis Lua沙盒没有os库,而且无论如何,当使用基于脚本的复制(在v3.2之前唯一可用的模式)时,Redis的math.random被设计为产生相同的值。 - Itamar Haber
@Itamar 嗯,它可以是任何不断变化的东西。不一定是os.time - warspyking
2个回答

5

如果你在调用Lua脚本时,最好的做法是将时间作为脚本参数传递。这样可以完全避免使用redis.call("TIME"),然后你可以使用当前时间设置种子。

local time = ARGV[1];
math.randomseed(time);
local millis = (tonumber(time[1]) * 1000) + math.floor(tonumber(time[2]) / 1000)
local version = string.format("%.0f",mills) .. string.format("%05d", math.random(99999))

这样做还可以避免复制的任何问题,因为所有实例都将接收相同的参数并生成相同的输出。

3
虽然原作者并没有实际提出问题,但我认为这是关于“如何在Redis Lua脚本中生成伪随机数”的最佳回答。请注意,我的翻译尽可能保留了原文的含义和风格。 - Blake Miller

2
我认为这种行为的原因是Redis试图防止在脚本内生成随机键,因为在复制过程中,这些脚本会被传输到副本(而不是数据本身)。因此,生成随机键可能导致不一致的副本。

这就是为什么在调用redis.call('TIME')之后,脚本中不允许对Redis进行写操作。

我猜想Redis中的Lua环境总是以同样的方式从math.random()返回相同的数字。


2
非常正确。但是,在v3.2中有一种新的复制模式改变了这种行为:http://redis.io/commands/eval#selective-replication-of-commands - Itamar Haber

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