ZSCAN保证在迭代期间得分发生变化的元素

时间:2016-09-19 12:54:00

标签: redis

我在文档中找不到这些信息:Redis是否保证在这种情况下使用ZSCAN命令返回一个元素:

  

元素从开头到结尾包含在有序集中   一个完整的迭代,但这个元素的分数已经改变(甚至   迭代期间,例如由另一个客户端多次?

我发现的相关陈述如下:

  

在a期间不会经常出现在集合中的元素   完整的迭代,可以返回或不返回:它是未定义的。

但我不知道在这种情况下分数变化是否与删除/添加操作相同。

2 个答案:

答案 0 :(得分:1)

如果元素在完整迭代期间存在,则zscan命令将返回该元素。在迭代过程中得分是否已经改变并不重要。

通常,zset实现为哈希表(即Redis' dict)和跳转列表。运行zscan命令时,它会遍历哈希表条目以执行扫描作业。分数的变化(字典条目的值)不会影响迭代过程。

如果zset足够小,Redis会将其实现为ziplist。在这种情况下,Redis会在单个zscan调用中返回所有元素。因此,在迭代过程中不能改变分数。

总之,您有保证。

答案 1 :(得分:0)

非常感谢 for_stack 进行确认。我不知道是否会有人回复,同时我在Java中实现了一些自己的检查:

@Test
public void testZScanGuaranteeWithScoreUpdates() {
    try (Jedis jedis = jedisPool.getResource()) {
        IntStream.rangeClosed(1, 50).forEach(i -> testZScanGuaranteeWithUpdates(jedis, false));
        IntStream.rangeClosed(1, 50).forEach(i -> testZScanGuaranteeWithUpdates(jedis, true));
    }
}

/**
 * Changing score of elements (named ids) during iteration (eventually inserting and removing another unchecked ids)
 * and then assert that no element (id) is missing
 */
private void testZScanGuaranteeWithUpdates(Jedis jedis, boolean noise) {
    Random ran = new Random();
    List<String> noiseIds = IntStream.range(0, 4000).mapToObj(i -> UUID.randomUUID().toString()).collect(toList());
    if (noise) { // insert some noise with random score (from 0 to 5000)
        noiseIds.forEach(id -> jedis.zadd(KEY, ran.nextInt(5000), id));
    }

    int totalIds = 2000;
    List<String> ids = IntStream.range(0, totalIds).mapToObj(i -> UUID.randomUUID().toString()).collect(toList());
    Set<String> allScanned = new HashSet<>();

    ids.forEach(id -> jedis.zadd(KEY, ran.nextInt(2500) + 1000, id)); // insert all IDs with random score (from 1000 to 3500)

    redis.scanIds(KEY, 100, res -> { // encapsulate iteration step - this closure is executed for every 100 elements during iteration
        allScanned.addAll(res); // record 100 scanned ids
        Collections.shuffle(ids);
        ids.stream().limit(500).forEach(id -> jedis.zadd(KEY, ran.nextInt(2500) + 1000, id)); // change score of 500 random ids
        if (noise) { // insert and remove some noise
            IntStream.range(0, 50).forEach(i -> jedis.zadd(KEY, ran.nextInt(5000), UUID.randomUUID().toString()));
            IntStream.range(0, 60).forEach(i -> jedis.zrem(KEY, noiseIds.get(ran.nextInt(noiseIds.size()))));
        }
    });

    if (!noise) {
        assertEquals(totalIds, allScanned.size()); // 2000 unique ids scanned
    }
    assertTrue(allScanned.containsAll(ids)); // none id is missing

    jedis.del(KEY); // prepare for test re-execution
}

测试正在通过,即ZSCAN返回所有元素,即使在使用ZADD命令迭代期间其分数发生了变化。