在Redis中,是否可以跨多个排序集对成员进行排序?

时间:2015-12-10 20:38:06

标签: redis

我正在Redis中的多个排序集中跟踪成员,作为对成员进行多列索引的方法。举个例子,假设我有两个排序集lastseen(这是一个纪元时间)和points,我将用户名存储为这些排序集中的成员。

我想首先按lastseen进行排序,以便我可以在最后一天或一个月内看到用户,然后我想要按points对结果成员进行排序,这样我才能有效地在最后一天或一个月内按成分排列的成员。

如果我可以将对ZREVRANGEBYSCORE的调用结果存储到新的排序集(我们将调用新的排序集temp),这将很容易,因为那时我可以对lastseen进行排序限制,将结果存储到temp,对temp使用ZINTERSTORE,对points使用权重为零的out(存储到result),最后使用ZREVRANGEBYSCORE再次result。但是,Redis中没有内置的方法可以将ZRANGE的结果存储到新的排序集中。

我研究过使用here发布的解决方案,虽然它确实可以正确排序结果,但排序集中的结果分数不能再用于根据时间准确地限制结果(即仅想要在最后一天内完成。)

例如:

redis> ZADD lastseen 12345 "foo"
redis> ZADD lastseen 12350 "bar"
redis> ZADD lastseen 12355 "sucka"
redis> ZADD points 5 "foo"
redis> ZADD points 3 "bar"
redis> ZADD points 9 "sucka"

如果我的时间窗口在1234912356之间,我希望最终得到的是成员列表['sucka', 'bar']

2 个答案:

答案 0 :(得分:1)

对于复杂的查询,您希望使用其他处理语言来补充Redis的内置命令。最简单的方法是从你的后端语言中调用并使用它来处理。 Python中使用redis-py的一个例子是:

import redis
finish_time, start_time = 12356, 12349
r = redis.Redis(host='localhost', port=6379, db=0, password='some_pass')
entries_in_time_frame = r.zrevrangebyscore('lastseen', finish_time, start_time)
p = r.pipeline()
for entry in entries_in_time_frame:
    p.zscore('points', entry)
scores = zip(entries_in_time_frame, p.execute())
sorted_entries = [tup[0] for tup in sorted(scores, key=lambda tup: tup[1])]


>>> ['sucka', 'bar']

注意管道,所以我们只向Redis服务器发送两次调用,因此网络延迟不应该让我们慢下来。如果你需要更快(可能如果第一个ZREVRANGEBYSCORE返回的内容很长),你可以重写与上面as a Lua script相同的逻辑。这是一个有效的例子(注意我的lua是生锈的,所以可以优化):

local start_time = ARGV[1]
local finish_time = ARGV[2]
local entries_in_time_frame = redis.call('ZREVRANGEBYSCORE', KEYS[1], finish_time, start_time)

local sort_function = function (k0, k1)
   local s0 = redis.call('ZSCORE', KEYS[2], k0)
   local s1 = redis.call('ZSCORE', KEYS[2], k1)
   return (s0 > s1)
end

table.sort(entries_in_time_frame, sort_function)

return entries_in_time_frame

您可以这样称呼它:

redis-cli -a some_pass EVAL "$(cat script.lua)" 2 lastseen points 12349 12356

返回:

1) "bar"
2) "foo"

答案 1 :(得分:1)

我能想到的解决方案是:

1)你的愿望是ZREVRANGEBYSCORE并以某种方式保存临时结果。相反,您可以复制zset(可以使用ZINTERSTORE只用一个参数作为参数完成),然后在新副本上执行ZREMRANGEBYSCORE以摆脱您不感兴趣的时间在,然后做最后的ZINTERSTORE

2)正如Eli建议的那样,在客户端循环执行。

3)在Lua脚本中执行相同的操作。

这些都是潜在的昂贵操作,因此最好的方法取决于您的数据和用例。如果不了解更多,我会亲自倾向于Lua解决方案。