答:一般来说,我们接触最多的是使用 keys 来进行查询,keys 使用的话也比较简单,但是在正式环境中话必须禁止使用 keys, 而是推荐使用 scan.
keys 的使用
语法:keys PATTERN
:用于查找所有符合给定模式 PATTERN 的 key
keys * # 查询 redis 中所有的 key keys h?llo # ?: 通配单个字符, 可以是 hello hallo ..., 不包含 hllo. keys h*llo # * 通配任意多个字符, hllo hello heello heallo ... keys h[ae]llo # []: 通配括号内的某一个字符,可以查询出 hello hallo, 不可以查出 hllo. keys h[^e]llo # 匹配 e 之前的字母, 可以是 hallo ... hdllo ,不包含 hello. keys h[a-b]llo # - 相当于 or, 匹配 a 或 b, 可以是 hallo hbllo; 可以添加更多的条件,比如: keys h[a-b-c-d]llo # 上面的语法还可以进行相应的组合,如 keys h[a-b]*llo # 匹配 ha(任意多个字符)llo 和 hb(任意多个字符)llo
正式环境禁用keys的原因
keys 的时间复杂度为:O(N), 其中N为数据库中密钥的数目,假设数据库中的密钥名称和给定模式的长度有限。尽管其复杂的为 O(N), 但是持续的时间却很短,在一个入门级别的电脑上可以做到 40ms 扫描 100 万个数据。
但是如果是高并发的条件下,使用 keys 就会出现问题,因为 Redis 是一个单线程的数据库,每次执行命令都会对数据库进行加锁,并且 keys 命令没有分页功能,每次都会遍历整个数据库
,假如 20ms 执行完一次操作,如果是百万级并发,那么 Redis 每次执行命令都会出现短暂的锁住,进而导致大量的请求被堵塞,导致其他业务不可用,进而造成 CPU 使用率高,最后造成服务器宕机。
keys 存在的问题:
- 没有分页功能,一次会遍历所有的数据库,并查询出所有符合条件的 key 值;但是查询的结果有用的有可能很少那就很耗资源。
- 尽管查询速度很快,但随着数据量的增长,查询的时间也会变得越长。
SCAN 的使用
语法:SCAN cursor [MATCH pattern] [COUNT count] [TYPE type]
SCAN是基于游标的迭代器。这意味着在每次调用该命令时,服务器都会返回相应的查询数据和一个新的游标,用户需要将该游标用作下一个调用中的游标参数。
游标设置为0时,迭代将开始,服务器返回的游标为0时,迭代将终止。
参数说明:
cursor
: 游标位置(hash
桶的索引值),整数值;从 0 开始,到 0 结束;查询的结果有可能是0个,但游标不为 0, 只要游标不为 0,就代表遍历还没有结束。match pattern
: 正则匹配字段 (可选)count
: 限定单次扫描的数量 limit hint(参考值,底层遍历的数量不一定),默认为10。其并不是查询结果返回的最大数量。比如 count 为 10000,意味着每次扫描 1w 条记录,但是有可能只有 10 条符合条件或者有 2w 条记录符合。(可选)
注意事项:
- 游标是一个 Hash 值,是乱序的,一个遍历从 从 0 开始,到 0 结束,意味着重新返回起点。
- 无需为每次迭代使用相同的 COUNT 值。只要在下一次遍历时是传入上一次的获得的游标即可,这样下一次遍历会使用上一次的 COUNT 值。
- scan 返回的结果可能会有重复数据,需要客户端去重
- 新增的数据有可能没有被遍历到
127.0.0.1:6379> scan 0 match 1* count 15 1) "17" 2) 1) "key:12" 2) "key:18" 3) "key:14" 4) "key:14" 5) "key:16" 6) "key:17" 7) "key:15" 8) "key:10" 9) "key:13" 10) "key:17" 11) "key:1" # 扫描 15 个元素,但是只有 11 个元素符合 127.0.0.1:6379> scan 17 # count 会使用上一次的 15 1) "0" # 游标值为 0,意味着遍历结束 2) 1) "key:15" 2) "key:118" 3) "key:10" 4) "key:112" 5) "key:119" 6) "key:13" 7) "key:16" 8) "key:19" 9) "key:111"