Redis性能优化:使用scan命令替换keys

版权声明:非商业目的可自由转载,转载请标明出处 https://blog.csdn.net/u010013573/article/details/87932576

keys命令

  • keys命令用于返回指定的正则表达式所匹配的所有key的列表,其所检索的是Redis当前所使用的数据库(默认为0号数据库)的所有key,用法如下:

    1. *匹配数据库中所有key 
    KEYS *
    2. ?匹配某个字符
    KEYS t?st
    3. *和其他字符一起使用,如下匹配test, teest等
    KEYS t*st
    4. []匹配某些字符中的一个,如下匹配test或tast
    KEYS t[ae]st
    
  • 由于每个Redis实例是使用单线程处理所有请求的,故keys命令和其他命令都是在同一个队列排队等待执行的,如果keys命令执行时间长,则会阻塞其他命令的执行,导致性能问题。如果keys命令需要匹配非常多的key,则可能造成长期停顿。

  • 在Java中keys命令通常使用RedisTemplate来调用,如下:

    String pattern = "*";
    redisTemplate.keys(pattern)
    

scan命令

  • scan命令是在2.8及之后版本提供的,主要用于解决keys命令可能导致整个Redis实例停顿的问题。scan是一种迭代命令,主要是对keys命令进行了分解,即原本使用一个keys请求一次匹配获取所有符合的key的操作,分解了多次scan操作,每次scan操作返回匹配的key的一个子集,这样每个scan请求的操作时间很短,多次scan请求之间可以执行其他命令,故减少对其他命令执行的阻塞,直到最后一个scan请求发现没有数据可返回了,则操作完成,汇总该次所有scan请求的数据,从而达到与keys命令一次获取的数据相同。

  • 由于scan命令需要执行多次,即相当于执行了多个命令,存在多次命令请求和响应周期,故整体执行时间可能要比keys命令长。

  • 在使用scan命令时,通常需要指定一个count参照,如下:0为游标,第二次迭代从第一次迭代返回的游标开始,COUNT为1000表示提示hint此次需要返回1000个数据,但实际并不一定,可能更多也可能更少。在多次迭代中COUNT可以指定不同的值。COUNT默认值为10.

    127.0.0.1:6379> scan 0 MATCH user_* COUNT 1000
    1) "0"
    2) (empty list or set)
    
  • 游标的使用:第一次返回17,则第二次可以从17开始。

    127.0.0.1:6379> scan 0
    1) "17"
    2)  1) "key:12"
        2) "key:8"
        3) "key:4"
        4) "key:14"
        5) "key:16"
        6) "key:17"
        7) "key:15"
        8) "key:10"
        9) "key:3"
        10) "key:7"
        11) "key:1"
    
    127.0.0.1:6379> scan 17
    1) "0"
    2) 1) "key:5"
       2) "key:18"
       3) "key:0"
       4) "key:2"
       5) "key:19"
       6) "key:13"
       7) "key:6"
       8) "key:9"
       9) "key:11"
    

Java使用scan命令示例

  • 在Java中一般基于spring的RedisTemplate来进行Redis相关操作,其中RedisTemplate是线程安全的,故整个项目可以共享一个RedisTemplate实例。scan代码示例如下:
    public int getTotalUsers() {
        Set<String> allUsers = redisTemplate.execute(new RedisCallback<Set<String>>() {
          @Override
          public Set<String> doInRedis(RedisConnection connection) throws DataAccessException {
            Set<String> partUers = new HashSet<>();
            // 放在try中自动释放cursor
            try (Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder()
                    .match(RedisStorage.USER_KEY + "*").count(50000).build())) {
              while (cursor.hasNext()) {
                partUers.add(new String(cursor.next()));
              }
            } catch (IOException e) {
              LOG.error("getTotalUsers cursor close {}", e);
            }
            return partUers;
          }
        });
        return sessions.size();
    }
    

猜你喜欢

转载自blog.csdn.net/u010013573/article/details/87932576