优化 Redis 中 ZRANGEBYSCORE 命令的执行时间。

我正在使用 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 脚本在排序集中有 6K 个成员时需要 24 秒时间。

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. [键数]
  2. foo1 排序集名称,具有 6k 个成员。
  3. foo2 排序集名称。
  4. foo3 排序集。其中从 foo1 到 foo2 的成员被移动。
  5. 当前时间戳。
  6. 当前时间戳 - 6 分钟。

有没有优化所需时间的方法?通过缓存 Lua 脚本或其他方式?

由于我总是从较低分数向较高分数获取值,使用 ZRANGE 取代 ZRANGEBYSCORE 会有帮助吗?

更多细节:

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

  1. foo1 包含成员,其分数是它们需要触发的时间戳。
  2. 每当需要在将来触发事件时,它会在 foo1 中放置,其得分为计划时间。
  3. 我的脚本从 foo1 中读取当前时间戳小于等于的所有成员(一个接一个),并将它们移动到 foo3。这是一次次完成的,并移动元素。此元素分配给某个工作者。 (已隐藏)
  4. 如果工作者完成了工作。它会从 foo3 中删除成员。
  5. 如果在 x 秒超时后成员未被删除 foo3。脚本会将其移回 foo1。以便它可以重新分配给其他工作者。
点赞
用户204011
用户204011

你发布的代码存在一些问题。以下代码为 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 行,element 永远不会是 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

-- 检查是否有任务超时
-- 如果是,则获取一条最早的任务并重新排队
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

-- 检查是否有任务需要执行
-- 如果是,则获取 10 条最早的任务并执行
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,否则帮不了你更多。

2013-12-03 11:39:36