【问题标题】:In Redis, is it possible to sort a member across multiple Sorted Sets?在 Redis 中,是否可以跨多个 Sorted Set 对成员进行排序?
【发布时间】:2016-03-16 15:25:21
【问题描述】:

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

我想先按lastseen 排序,这样我就可以在最后一天或一个月内看到用户,然后我想按points 对结果成员进行排序,这样我就可以有效地看到其中的成员按点排序的最后一天或一个月。

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

我研究过使用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"

假设我的时间窗口在12349 和12356 之间,我想结束的是成员列表['sucka', 'bar']。

【问题讨论】:

  • 我没有在 Redis 中存储历史数据。例如,我不是每次看到用户时都存储,只是最后一次。这意味着,每次成员登录时,我都会更新 lastseen 排序集,其中用户名作为成员,当前纪元时间作为分数。
  • 我的意思是,我没有在 Redis 中存储时间序列数据,只存储最新的。是的,排序集在启动时从 PostgreSQL 填充,但我更愿意实时查询 Redis 而不是 PostgreSQL。无论如何,它并没有真正改变我的问题——我可以很容易地使用一个展示相同用例的不同示例。
  • 我已经在 OP 中有这个了。

标签: redis


【解决方案1】:

我能想到的解决办法是:

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

2) 按照 Eli 的建议,在客户端循环执行。

3) 在 Lua 脚本中做同样的事情。

这些都是潜在的昂贵操作,因此最有效的操作将取决于您的数据和用例。如果不知道更多,我个人会倾向于 Lua 解决方案。

【讨论】:

  • 关于 1) 除非我遗漏了什么,否则使用 ZINTERSTORE 制作副本并没有帮助,因为副本上的 ZREVRANGEBYSCORE 仍然不会以我可以用于最终 ZINTERSTORE 的方式给我结果.关于 2) 对于我的用例,我必须将分数加权 1E10,然后将其与 lastseen 分数相加,以使结果正确排序,这确实使分数不合时宜,无法根据时间进行限制。
  • @Bryan: 1) REM,而不是REV。您正在删除副本中不在您想要的时间范围内的所有内容。 2)我现在明白了。您链接到的问题是进行不同类型的排序,第二列打破了第一列的关系。而您只想将时间用作过滤器,但排序完全基于点。所以这种方法行不通。
  • 天哪,我不明白我是如何错过了 ZREM* 命令甚至存在的事实!我认为这是你的错字。 ? 明天我会试试,然后在这里发回。
  • 这确实有效......我将其标记为已接受的答案,因为我最初的问题是关于在 Redis 中完成这项工作。虽然从技术上讲,使用 Lua 完成它也可以在 Redis 中完成,但这种方法不需要做任何额外的事情。无论它是更快还是更慢的解决方案都是另一回事......我还没有测试过。
  • @Bryan:乐于助人。关于性能,我认为这是一个问题,即(可能)更快的本机操作(与 Lua 相比)是否补偿了它所需的额外数据混洗。我认为没有基准测试是不可能的。
【解决方案2】:

对于如此复杂的查询,您希望使用另一种处理语言来补充 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"

【讨论】:

  • 这就是我所害怕的......不是问题,只是好奇是否可以直接在Redis中完成。
  • @Bryan 介意分享一下为什么你更喜欢它而不是 lua 脚本?
  • 我不一定喜欢它而不是 Lua 脚本,只是好奇它是否可以在没有的情况下完成。我说“害怕”有点误导。 ?
  • 我以前从未在 Redis 中使用过 Lua。如果不为我做这项工作,除了您提供的链接中的内容之外,关于如何开始使用此特定用例的 Lua 脚本的任何提示?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多