使用spring redis模板的SCAN命令

时间:2015-09-23 04:37:12

标签: spring redis spring-data

我正在尝试执行"扫描"使用RedisConnection命令。我不明白为什么以下代码抛出NoSuchElementException

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

例外:

  

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

3 个答案:

答案 0 :(得分:2)

我正在使用spring-data-redis 1.6.0-RELEASE和Jedis 2.7.2;我确实认为ScanCursor实现在这个版本上处理这种情况时会有轻微的缺陷 - 我还没有检查过以前的版本。

所以:解释相当复杂,但在ScanOptions对象中有一个需要设置的“count”字段(默认为10)。此字段包含此搜索的“意图”或“预期”结果。正如所解释的(不是很清楚,恕我直言)here,您可以在每次调用时更改count的值,尤其是在没有返回结果的情况下。我理解这是“工作意图”所以如果你没有得到任何回报,也许你的“关键空间”是巨大的,并且SCAN命令没有“足够努力”。显然,只要你得到结果,你就不需要增加它。

“简单但危险”的方法是拥有非常大的数量(例如100万或更多)。这将使REDIS消失,试图搜索你的巨大密钥空间,以找到“至少或接近尽可能多”的数量。不要忘记 - REDIS 是单线程所以你刚刚杀死了你的表现。尝试使用REDIS的12M键,你会发现尽管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,你将重新开始(并且基本上会做一些工作两次)。

当然,总是欢迎更好的解决方案:)

答案 1 :(得分:0)

是的,我在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.
    }

答案 2 :(得分:-1)

我的旧代码

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

工作代码

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