我一直在玩redis来跟踪分布式系统中外部api的速率限制。我决定为每个存在限制的路线创建一个密钥。密钥的值是在限制重置之前我仍可以进行多少次请求。并且通过将键的TTL设置为限制将重置时进行重置。
为此,我写了以下lua脚本:
if redis.call("EXISTS", KEYS[1]) == 1 then
local remaining = redis.call("DECR", KEYS[1])
if remaining < 0 then
local pttl = redis.call("PTTL", KEYS[1])
if pttl > 0 then
--[[
-- We would exceed the limit if we were to do a call now, so let's send back that a limit exists (1)
-- Also let's send back how much we would have exceeded the ratelimit if we were to ignore it (ramaning)
-- and how long we need to wait in ms untill we can try again (pttl)
]]
return {1, remaining, pttl}
elseif pttl == -1 then
-- The key expired the instant after we checked that it existed, so delete it and say there is no ratelimit
redis.call("DEL", KEYS[1])
return {0}
elseif pttl == -2 then
-- The key expired the instant after we decreased it by one. So let's just send back that there is no limit
return {0}
end
else
-- Great we have a ratelimit, but we did not exceed it yet.
return {1, remaining}
end
else
return {0}
end
Since a watched key can expire in the middle of a multi transaction without aborting it。我假设lua脚本的情况也是如此。因此,我将ttl为-1或-2时的情况放入。
在我编写该脚本后,我在eval命令页面上进行了更深入的研究,发现lua脚本必须是pure function。
在那里说
脚本必须始终使用相同的Redis写命令来评估 给定相同输入数据集的相同参数。执行的操作 由脚本不能依赖任何隐藏(非显式)信息 或者在脚本执行过程中或之间可能发生变化的状态 脚本的不同执行,也不依赖于任何外部 来自I / O设备的输入。
通过这个描述,我不确定我的函数是否是纯函数。
答案 0 :(得分:3)
在Itamar的回答之后,我想为自己确认一下,所以我写了一个小的lua脚本来测试它。脚本创建一个10ms TTL的密钥并检查ttl,直到它小于0:
redis.call("SET", KEYS[1], "someVal","PX", 10)
local tmp = redis.call("PTTL", KEYS[1])
while tmp >= 0
do
tmp = redis.call("PTTL", KEYS[1])
redis.log(redis.LOG_WARNING, "PTTL:" .. tmp)
end
return 0
当我运行此脚本时,它永远不会终止。它只是继续垃圾邮件我的日志,直到我杀死了redis服务器。但是,当脚本运行时,时间不会停滞不前,而是一旦TTL为0,它就会停止。
所以关键时代,它永远不会过期。
答案 1 :(得分:1)
由于观看的密钥可以在多事务中间到期而不会中止。我假设lua脚本的情况也是如此。因此,我将ttl为-1或-2时的情况放入。
与Lua脚本不同的AFAIR - 当脚本运行时,时间有点停止(至少在TTL方面)。
通过这个描述,我不确定我的函数是否是纯函数。
你的剧本很棒(实际上没有试图理解它的作用),不要担心:)