【发布时间】:2016-09-20 02:03:02
【问题描述】:
我在文档中找不到此信息:Redis 是否保证在此条件下使用 ZSCAN 命令返回元素:
该元素从头到尾都包含在排序集中 一个完整的迭代,但这样的元素的分数已经改变(甚至 在迭代期间多次,例如由另一个客户端)?
我发现的唯一相关声明如下:
在某个时间段内不经常出现在集合中的元素 完整的迭代,可以返回也可以不返回:它是未定义的。
但我不知道这种情况下的分数变化是否与删除/添加操作相同。
【问题讨论】:
标签: redis
我在文档中找不到此信息:Redis 是否保证在此条件下使用 ZSCAN 命令返回元素:
该元素从头到尾都包含在排序集中 一个完整的迭代,但这样的元素的分数已经改变(甚至 在迭代期间多次,例如由另一个客户端)?
我发现的唯一相关声明如下:
在某个时间段内不经常出现在集合中的元素 完整的迭代,可以返回也可以不返回:它是未定义的。
但我不知道这种情况下的分数变化是否与删除/添加操作相同。
【问题讨论】:
标签: redis
如果元素在整个迭代过程中存在,它将由zscan 命令返回。迭代过程中分数是否发生变化无关紧要。
通常,zset 实现为哈希表(即 Redis 的 dict)和跳过列表。运行zscan 命令时,它会遍历哈希表条目以执行扫描作业。分数(dict entry的值)的变化不会影响迭代过程。
如果zset 足够小,Redis 将其实现为ziplist。在这种情况下,Redis 在单个 zscan 调用中返回所有元素。所以在迭代过程中分数不能改变。
总之,你有保证。
【讨论】:
非常感谢 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 命令的迭代期间发生了变化。
【讨论】: