Redis 的 Keys 命令性能问题

Keys Pattern

keys 命令用于查找所有符合模式的key。

规则

  • keys * 匹配数据库中所有 keys
  • keys ? 匹配单个字符
    • keys h?llo 匹配 hello、 hallo、hxllo 等
  • keys * 匹配多个字符
    • keys h*llo 匹配 hllo、heeeello 等
  • keys h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo 实际应用中有时候会出现需要遍历redis中的所有键值的需求,比如清理没用的键等等。 但是 keys 这个命令性能真的很差,redis官方文档是这么描述的:

    Warning: consider KEYS as a command that should only be used in production environments with extreme care. It may ruin performance when it is executed against large databases. This command is intended for debugging and special operations, such as changing your keyspace layout. Don’t use KEYS in your regular application code. If you’re looking for a way to find keys in a subset of your keyspace, consider using SCAN or sets.

因为 Keys 会引发 Redis 锁,并且增加 Redis 的 CPU 占用,情况是很恶劣的。KEYS 命令不能用在生产的环境中,这个时候如果数量过大效率是十分低的。同时也不要用 KEYS 正则匹配,官方建议直接用集合类型。

  • KEYS 命令的性能随着数据库数据的增多而越来越慢
  • KEYS 命令会引起阻塞,连续的 KEYS 命令足以让 Redis 阻塞 试想如果 Redis 阻塞超过 10 秒,如果有集群的场景,可能导致集群判断 Redis 已经故障,从而进行故障切换; 以上的情况严重会导致应用程序出现雪崩的情况。

SCAN 命令

Redis2.8 版本之后 SCAN 命令已经可用,允许使用游标从 keyspace 中检索键。对比 KEYS 命令,虽然 SCAN 无法一次性返回所有匹配结果,但是却规避了阻塞系统这个高风险,从而也让一些操作可以放在主节点上执行。

需要注意的是,SCAN 命令是一个基于游标迭代器。SCAN 命令每次被调用之后, 都会向用户返回一个新的游标,用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程。同时,使用SCAN,用户还可以使用 keyname 模式和 count 选项对命令进行调整。SCAN 相关命令还包括 SSCAN 命令、HSCAN 命令和 ZSCAN 命令,分别用于集合、哈希键及有续集等。

  • 复杂度虽然也是 O(n),通过游标分步进行不会阻塞线程;

  • 有限制参数 COUNT ;

  • 同 keys命令 一样提供模式匹配功能;

  • 服务器不需要为游标保存状态,游标的唯一状态就是 scan 返回给客户端的游标整数;

    SCAN 用法

    SCAN cursor [MATCH pattern] [COUNT count]
    
  • scan 命令提供三个参数,第一个是 cursor,第二个是要匹配的正则,第三个是单次遍历的槽位
  • 第一个遍历是 cursor 值为0,然后将返回结果的第一个整数作为下一个遍历的游标,如果最后返回的到 cursor 的值为 0 就代表结束。
    127.0.0.1:6379> scan 0 MATCH tony* 
    1) "42"
    2)  1) "tony25"
      2) "tony2519"
      3) "tony2529"
      4) "tony2510"
      5) "tony2523"
      6) "tony255"
      7) "tony2514"
      8) "tony256"
      9) "tony2511"
     10) "tony15"
    127.0.0.1:6379> scan 42 MATCH tony* COUNT 1000
    1) "0"
    2)  1) "tony3513"
      2) "tony359"
      3) "tony4521"
      4) "tony356"
      5) "tony30"
      6) "tony320"
      7) "tony3"
      8) "tony312"
    
    返回分为两个部分如上面的代码中, 1)代表下一次迭代的游标,2)代表本次迭代的结果集,注意如果返回游标为0就代表全部匹配完成。

    批量删除scan命令

    因为 KEYS 命令的时间复杂度为 O(n),而 SCAN 命令会将遍历操作分解成 m 次,然后每次去执行,从而时间复杂度为 O(1)。也解决使用 keys 命令遍历大量数据而导致 Redis 服务器阻塞的情况。所以建议使用下边的指令进行批量的删除操作:
    redis-cli --scan --pattern "key前缀*" | xargs -L 1000 redis-cli del
    

总结

1、 数据分离

不要什么都往 Redis 中放,尽量放些 QPS 比较高的数据,内存的开销很昂贵的,可以考虑硬盘存放。

2、分业务

不同的实例单独放这样存取的时候方便些,故障的时候也不会影响其他的实例。

3、压缩

redis 中有很大的单个 key 的值建议压缩成二进制存放。

4、失效时间

redis 中设置 key 的失效时间,如果不设置会一直占用着内存,而且 key 的失效时间应该根据业务场景来设置。

5、容量

占用内存不要太大 10-20G,其次键的数量控制在 1 千万以内。

6、监控

运维合理的监控好数据,做好 Redis 安全漏洞的防护和灾备。