【问题标题】:Redis optimize time taken by ZRANGEBYSCORERedis 优化 ZRANGEBYSCORE 花费的时间
【发布时间】:2016-04-12 09:02:42
【问题描述】:

我有一个使用 redis 排序集的队列系统。我的 lua 脚本如下所示:

local moveElement = function(source, dest , score, destscore)
    local element = redis.pcall('ZRANGEBYSCORE', source, '-inf',score, 'WITHSCORES' , 'LIMIT' , '0' , '1')
    if element ~= false and #element ~= 0 then
        redis.call('ZADD' , dest , destscore , element[1])
        redis.call('ZREM' , source , element[1])
    end
end
local temp = moveElement(KEYS[2], KEYS[1] , ARGV[2])
local temp = moveElement(KEYS[3], KEYS[1] , ARGV[2])
local score= redis.call('ZRANGEBYSCORE', KEYS[1], '-inf',ARGV[1], 'WITHSCORES' , 'LIMIT' , '0' , '10')
if score ~= false and #score ~= 0 then
    local i = 1
    while i<=#score do
        redis.call('ZREM', KEYS[1] , score[i])
        redis.call('ZREM', KEYS[2] , score[i])
        redis.call('ZADD', KEYS[3], ARGV[1] , score[i])
        i=i+2
    end
end
return score

这个 lua 脚本需要 24 秒,排序集中有 6K 成员。

SLOWLOG GET 10

1) 1) (integer) 5937
   2) (integer) 1385993558
   3) (integer) 24446
   4) 1) "EVAL"
      2) "local moveElement = function(source, dest , score, destscore)                 local element = redis.pcall('ZRANGEBYSCORE', sourc... (937 more bytes)"

我的代码做法是:

我用以下参数调用这个脚本。

    1. [键数]
  1. foo1 排序集名称有 6k。
  2. foo2 排序集名称。
  3. foo3 排序集。将成员从 foo1 移动到 foo2 的位置。
  4. 当前时间戳。
  5. 当前时间戳 - 6 分钟。

有没有办法优化时间?通过缓存 lau 脚本什么的?

因为我总是使用 ZRANGE 帮助代替 ZRANGEBYSCORE 从低到高获取价值?

更多详情:

我正在尝试实现某种类型的队列系统。逻辑在这里:

  1. foo1 包含具有分数作为时间戳的成员,在该成员 需要被解雇。
  2. 将来需要触发事件时。它被放入 foo1 中,得分为调度时间。
  3. 我的脚本从 foo1 读取分数小于或等于当前时间戳的所有成员(一个接一个)并将它们移动到 foo3。这是一一完成的,并且元素被移动。这个元素被分配给分配给一些工人。 (隐藏)
  4. 如果工人完成工作。它从 foo3 中删除成员。
  5. 如果成员未删除 foo3,则在 x 秒超时后。脚本将其移回 foo1。所以它可以重新分配给其他工人。

【问题讨论】:

    标签: lua redis


    【解决方案1】:

    您发布的脚本存在一些问题。这是在 Lua 中的(请不要发布带引号的字符串,这会使代码难以阅读):

    local moveElement = function(source, dest , score, destscore)
      local element = redis.pcall(
        'ZRANGEBYSCORE', source, '-inf', score, 'WITHSCORES' , 'LIMIT' , '0' , '1'
      )
      if element ~= false and #element ~= 0 then
        redis.call('ZADD' , dest , destscore , element[1] )
        redis.call('ZREM' , source , element[1])
      end
    end
    
    local temp = moveElement(KEYS[2], KEYS[1] , ARGV[2])
    local temp = moveElement(KEYS[3], KEYS[1] , ARGV[2]) 
    local score= redis.call(
      'ZRANGEBYSCORE', KEYS[1], '-inf', ARGV[1], 'WITHSCORES' , 'LIMIT' , '0' , '10'
    )
    if score ~= false and #score ~= 0 then
      local i = 1
      while i<=#score do
        redis.call('ZREM', KEYS[1] , score[i])
        redis.call('ZREM', KEYS[2] , score[i])
        redis.call('ZADD', KEYS[3], ARGV[1] , score[i])
        i=i+2
      end
    end
    return score
    

    首先,第 5 行,元素永远不会是 false。我怀疑您正在尝试捕获错误,但在这种情况下,它将是一个带有 err 字段的表。 score 也一样。

    然后,您分配两个本地 temp 变量,以后不再使用它们。

    while 循环最好用for 编写:

    for i=1,#score,2 do
      redis.call('ZREM', KEYS[1] , score[i])
      redis.call('ZREM', KEYS[2] , score[i])
      redis.call('ZADD', KEYS[3], ARGV[1] , score[i])
    end
    

    为什么在 moveElement 中使用WITHSCORES?你不使用它。

    除此之外,您能否更清楚地解释您想要实现的目标,以便我可以进一步帮助您?无论如何,这样的操作肯定不会花费 24 秒(除非您可能在一台古董机器上运行)。

    编辑

    考虑到您到目前为止告诉我的内容,这是一个更简单的脚本版本:

    local now = ARGV[1]
    local timed_out = ARGV[2]
    local pending_jobs = KEYS[1]
    local running_jobs = KEYS[2]
    local jobs
    
    local not_empty = function(x)
      return (type(x) == "table") and (not x.err) and (#x ~= 0)
    end
    
    -- check if some jobs have timed out
    -- if yes, take the oldest one and queue it again
    jobs = redis.pcall(
      'ZRANGEBYSCORE', timed_out, '-inf', timed_out, 'LIMIT', '0', '1'
    )
    if not_empty(jobs) then
      redis.call('ZADD', pending_jobs, now, jobs[1])
      redis.call('ZREM', running_jobs, jobs[1])
    end
    
    -- check if there are jobs ready
    -- if yes, take the 10 oldest ones and run them
    jobs = redis.pcall(
      'ZRANGEBYSCORE', KEYS[1], '-inf', ARGV[1], 'LIMIT', '0', '10'
    )
    if not_empty(jobs) then
      for i=1,#jobs do
        redis.call('ZREM', pending_jobs, jobs[i])
        redis.call('ZADD', running_jobs, now, jobs[i])
      end
    end
    
    return jobs
    

    看看这个脚本,我完全看不出它为什么要花 24 秒。所以要么:

    • 您的 Redis 设置有问题,或者
    • 您实际上并没有这样做,或者
    • 您的测量结果有误。

    除非您可以提供带有显示此问题的数据的 .rdb,否则我无话可说...

    【讨论】:

    • @catweel 您的所有积分都有效。我会修复提到的事情。我有更新问题,其中包含有关我要实现的目标的更多详细信息。关于 WITHSCORES :我之前正在使用它。在我发布问题前 5 分钟。然后我意识到 ARGV[1] 与 score 相比会是更好的选择。
    • 我还是不明白。 moveElement 有 4 个参数,为什么用 3 个参数来称呼它? foo2 是干什么用的?
    • @catewell 只有这两个是混淆吗? 1. 我用 4 个参数调用 moveElement。 4 1 是 ARGV[1](当前时间戳)。我想,我错误地删除了它。 2. 请忽略 foo2。它的用途与 foo3 类似,但在某些不同的情况下。
    猜你喜欢
    • 1970-01-01
    • 2021-06-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-03
    • 1970-01-01
    相关资源
    最近更新 更多