【问题标题】:How to get same rank for same scores in Redis' ZRANK?如何在 Redis 的 ZRANK 中以相同的分数获得相同的排名?
【发布时间】:2019-02-08 15:45:15
【问题描述】:

如果我有 5 个成员,分数如下

a - 1
b - 2
c - 3
d - 3
e - 5

c 的 ZRANK 返回 2,d 的 ZRANK 返回 3

有没有办法以相同的分数获得相同的排名?
示例:ZRANK c = 2, d = 2, e = 3
如果是,那么如何在 spring-data-redis 中实现呢?

【问题讨论】:

    标签: redis spring-data-redis


    【解决方案1】:

    任何真正的解决方案都需要满足原始问题中缺少的要求。我的第一个答案假设一个小数据集,但这种方法至少在 O(N) 中完成密集排名(例如通过 Lua)时无法扩展。

    所以,假设有很多有分数的用户,那么for_stack建议的方向比较好,多个数据结构组合在一起。我相信这是他最后一句话的要点。

    要存储用户的分数,您可以使用哈希。虽然从概念上讲,您可以使用单个键来存储所有用户分数的哈希值,但实际上您希望对哈希值进行哈希处理,以便它可以扩展。为了简单起见,我将忽略哈希缩放。

    这是您在 Lua 中添加(更新)用户分数的方式:

    local hscores_key = KEYS[1]
    local user = ARGV[1]
    local increment = ARGV[2]
    local new_score = redis.call('HINCRBY', hscores_key, user, increment)
    

    接下来,我们要跟踪每个离散分值的当前用户数,因此我们为此保留另一个哈希:

    local old_score = new_score - increment
    local hcounts_key = KEYS[2]
    local old_count = redis.call('HINCRBY', hcounts_key, old_score, -1)
    local new_count = redis.call('HINCRBY', hcounts_key, new_score, 1)
    

    现在,我们需要维护的最后一件事是每个分数排名,并带有一个排序集。每个新分数都会作为成员添加到 zset 中,而没有更多用户的分数会被删除:

    local zdranks_key = KEYS[3]
    if new_count == 1 then
      redis.call('ZADD', zdranks_key, new_score, new_score)
    end
    if old_count == 0 then
      redis.call('ZREM', zdranks_key, old_score)
    end
    

    由于使用了 Sorted Set,这个 3-piece-script 的复杂度为 O(logN),但请注意 N 是离散分数值的数量,而不是系统中的用户。获取用户的密集排名是通过另一个更短更简单的脚本完成的:

    local hscores_key = KEYS[1]
    local zdranks_key = KEYS[2]
    local user = ARGV[1]
    
    local score = redis.call('HGET', hscores_key, user)
    return redis.call('ZRANK', zdranks_key, score)
    

    【讨论】:

    • as dense ranking is done (e.g. via Lua) in O(N) at least 为什么会这样?我可以想到一种使用 Lua(使用 ZSCORE 和 ZCOUNT)的方法,其中密集排名在 O(log(N)) 中完成。
    【解决方案2】:

    您可以使用两个排序集来实现目标:一个用于成员到得分的映射,另一个用于得分到排名的映射

    添加

    1. 将项目添加到成员以进行评分映射ZADD mem_2_score 1 a 2 b 3 c 3 d 5 e
    2. 将分数添加到分数到排名映射ZADD score_2_rank 1 1 2 2 3 3 5 5

    搜索

    1. 先获取分数:ZSCORE mem_2_score c,这应该返回分数,即3
    2. 获取分数的排名:ZRANK score_2_rank 3,这应该返回密集排名,即2

    为了以原子方式运行它,请将 AddSearch 操作包装到 2 个 Lua 脚本中。

    【讨论】:

    • 我已经尝试过这种方法。当我的数据是静态的时,它就像魅力一样。当我更新第一组成员的分数时会出现问题。例如:如果我将 b 的分数更新为 4 并在第二组中添加 4,zrank score_2_rank 4 将是 3 而不是 2,因为分数 2 仍然存在于集合中
    • 好点!为了删除或更新成员,您需要有一个 HASH 来保存分数的重新计数。添加成员时,增加其分数的重新计数。删除或更新成员时,decr 对旧分数的重新计数。如果分数为 0,也从 HASHscore_2_rank 中删除分数。这真的很复杂!
    • 在下面有我的乐趣(两次,不包括第一次),经过仔细考虑,我认为就计算复杂性而言,一组优于两组(但使用两个哈希而不是一个或没有)。也就是说,要扩展这个坏宝贝,您需要对哈希进行分片,可能还需要对 zset 进行分片,这意味着您需要做更多的工作:P
    • @ItamarHaber 是的,在 mem_2_score 映射的情况下,哈希比排序集更好,因为我们不需要顺序。
    【解决方案3】:

    然后是这个拉取请求 - https://github.com/antirez/redis/pull/2011 - 它已经死了,但似乎在运行中进行密集排名。最初的问题/功能请求 (https://github.com/antirez/redis/issues/943) 引起了一些兴趣,所以也许值得恢复它 /cc @antirez :)

    【讨论】:

    • 看来这个功能是非常需要的:)
    【解决方案4】:

    rank在有序集合中是唯一的,得分相同的元素按词法排序(排序)。

    没有 Redis 命令可以执行这种“密集排名”

    但是,您可以使用 Lua 脚本从排序集中获取范围,并将其缩减为您请求的形式。这可能适用于小型数据集,但您必须设计更复杂的东西才能进行扩展。

    【讨论】:

    • 是的,我实际上一直在思考这个问题,因为它恰好是另一个人在大致相同的时间但在另一个频道上提出的......我想我会添加另一个答案只是为了好玩
    【解决方案5】:
    unsigned long zslGetRank(zskiplist *zsl, double score, sds ele) {
        zskiplistNode *x;
        unsigned long rank = 0;
        int i;
    
        x = zsl->header;
        for (i = zsl->level-1; i >= 0; i--) {
            while (x->level[i].forward &&
                (x->level[i].forward->score < score ||
                    (x->level[i].forward->score == score &&
                    sdscmp(x->level[i].forward->ele,ele) <= 0))) {
                rank += x->level[i].span;
                x = x->level[i].forward;
            }
    
            /* x might be equal to zsl->header, so test if obj is non-NULL */
            if (x->ele && x->score == score && sdscmp(x->ele,ele) == 0) {
                return rank;
            }
        }
        return 0;
    }
    

    https://github.com/redis/redis/blob/b375f5919ea7458ecf453cbe58f05a6085a954f0/src/t_zset.c#L475

    这是 redis 用于计算排序集中排名的代码。现在,它只是根据 Skiplist 中的位置(根据分数排序)给出排名。

    What does the skiplistnode variable "span" mean in redis.h?(什么是跨度?)

    【讨论】:

      猜你喜欢
      • 2023-03-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多