使用Spring Redis模板的SCAN命令

5

我正在尝试使用RedisConnection执行“scan”命令。不明白为什么以下代码会抛出NoSuchElementException异常。

RedisConnection redisConnection = redisTemplate.getConnectionFactory().getConnection();
    Cursor c = redisConnection.scan(scanOptions);
    while (c.hasNext()) {
        c.next();
    }

异常:

java.util.NoSuchElementException 在 java.util.Collections$EmptyIterator.next(Collections.java:4189) 中 atorg.springframework.data.redis.core.ScanCursor.moveNext(ScanCursor.java:215)中 at org.springframework.data.redis.core.ScanCursor.next(ScanCursor.java:202)


为了使这个问题更加可见,你还应该标记为“spring-data”,“scan”以及可能的其他值。 - thanosa75
3个回答

4

是的,我已经尝试过这个方法,在1.6.6.RELEASE版spring-data-redis.version中。没有问题,下面的简单while循环代码就足够了。我将计数值设置为100(值越大),以节省往返时间。

    RedisConnection redisConnection = null;
    try {
        redisConnection = redisTemplate.getConnectionFactory().getConnection();
        ScanOptions options = ScanOptions.scanOptions().match(workQKey).count(100).build();

        Cursor c = redisConnection.scan(options);
        while (c.hasNext()) {
            logger.info(new String((byte[]) c.next()));
        }
    } finally {
        redisConnection.close(); //Ensure closing this connection.
    }

3
我正在使用spring-data-redis 1.6.0-RELEASE和Jedis 2.7.2;我认为在这个版本中,ScanCursor实现在处理此情况时略有缺陷——尽管我没有检查过以前的版本。
因此:比较难解释,但在ScanOptions对象中有一个需要设置的“count”字段(默认为10)。该字段包含此搜索的“意图”或“预期”结果。如此处所述(在我看来并不是很清晰),您可以在每次调用时更改count的值,特别是如果没有返回结果。我将其理解为“工作意图”,因此,如果您没有收到任何结果,则可能您的“键空间”非常广泛,而SCAN命令还没有“努力工作”。显然,只要您有返回结果,您就不需要增加此值。
一种“简单但危险”的方法是设置一个非常大的计数(例如100万或更多)。这将使REDIS尝试搜索您广泛的键空间,以找到“至少或接近于”您的大计数。请记住-REDIS是单线程的,因此您会影响性能。在具有1200万个键的REDIS上尝试此操作,您会发现虽然SCAN可以愉快地返回具有非常高计数值的结果,但它绝对不会在该搜索期间执行任何其他操作。
解决您问题的方案如下:
ScanOptions options = ScanOptions.scanOptions().match(pattern).count(countValue).build();
boolean done = false;

// the while-loop below makes sure that we'll get a valid cursor - 
// by looking harder if we don't get a result initially
while (!done) {
  try(Cursor c = redisConnection.scan(scanOptions)) {
    while (c.hasNext()) {
       c.next();
    }
    done = true; //we've made it here, lets go away
  } catch (NoSuchElementException nse) {
    System.out.println("Going for "+countValue+" was not hard enough. Trying harder");
    options = ScanOptions.scanOptions().match(pattern).count(countValue*2).build();
  }
}

请注意,Spring Data REDIS的ScanCursor实现会根据文档正确按照SCAN指令执行循环,并在需要时正确地循环到循环结束。我没有找到更改相同游标内的扫描选项的方法 - 因此,如果在结果的一半中出现NoSuchElementException,则有可能存在重新开始(并且基本上重复某些工作)的风险。
当然,更好的解决方案总是受欢迎的 :)

从 https://gist.github.com/thanosa75/00b1c14105f4f42495ab 获取灵感 - 这是一个简单的 shell 脚本,通过有效的游标循环调用 redis-cli 来检索模式匹配的键。 - thanosa75
最新的spring-data-redis版本解决了这个问题;请参见https://github.com/spring-projects/spring-data-redis/pull/154。 - thanosa75
Spring Data REDIS的ScanCursor实现会确保我们不会收到重复事件吗?因为SCAN文档在其缺点部分说明可能返回重复数据。 - Kanagavelu Sugumar

-2

我的旧代码

ScanOptions.scanOptions().match("*" + query + "*").count(10).build();

工作中的代码
ScanOptions.scanOptions().match("*" + query + "*").count(Integer.MAX_VALUE).build();

以上代码有什么问题?就像我第一个代码片段中的那样,匹配查询构建了小型数据集,例如10条记录。在这种情况下,游标尝试查找下一个元素。使用Integer.MAX_VALUE结果集适用于所有类型的数据集。 - Ramesh Papaganti
如我在上面的回答中提到的(https://dev59.com/CI_ea4cB1Zd3GeqPNmSH#33133756),REDIS是单线程的,因此您刚刚降低了性能。 - thanosa75

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接