问题描述
最近碰到一前同事在群里聊天,说因为错误使用keys
导致生产事故,为此他差点失业(玩笑),下面是他的自白(事件回顾):
问题分析
为什么测试,灰度环境使用keys没有导致任何问题,而到生产环境会导致服务挂了呢?
这个问题首先得从keys
这个命令说起。
先看下官方对这个命令对说明:
Available since 1.0.0.
Time complexity: O(N) with N being the number of keys in the database, under the assumption that the key names in the database and the given pattern have limited length.
Returns all keys matching pattern.
While the time complexity for this operation is O(N), the constant times are fairly low. For example, Redis running on an entry level laptop can scan a 1 million key database in 40 milliseconds.
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.
看不懂的同学我直接翻译下大致意思:
返回所有匹配的key.
keys
的时间复杂度是O(N),N为执行该命令下的数据库的key的数量,常数。
redis扫描key的速度很快,在入门笔记本大约是40毫秒100w个。
警告⚠️:keys
用在生产环境只能以极低频率执行。 在大数据库执行时会出现灾难性的性能。如果需要查询某些key,考虑使用SCAN
或者sets
。
一位老哥冒死执行的结果:
近1200w的key数量,执行时间约1.35s,大约100ms扫描100w的key。这速度比官方声明的要慢,可能是因为该服务器有其他应用占用了cpu。
这个命令为什么会这么慢呢?
换个问法,为什么redis需要遍历所有的key才能找到我们需要的key呢?
- Redis是NoSQL型数据库,以hash数据结构存储的,所以才能实现高效的数据查询。而hash结构对于精确查找是非常快的,对于模糊查询,则无能为力。
- Redis的命令执行是单线程的,同一时间只能执行单个命令。单一长时间命令会堵塞后续。(可以通过
debug sleep 0.1
100ms 模拟执行长时间命令)
以上两点造成了KEYS
进行key查询需要遍历当前db的所有数据,以及当该命令执行完成的时候后续命令都会被堵塞。
因此在redis中执行的命令,尽量避免长时间堵塞命令。
改进方法
使用SCAN cursor [MATCH pattern] [COUNT count]
命令以迭代的方式进行key遍历(限制单次查询的key数量)。
这个 count
不是限定返回结果的数量,而是限定服务器单次遍历的字典槽位数量(约等于)。
缺点:
1.同一个元素可能会被返回多次。 处理重复元素的工作交由应用程序负责, 比如说, 可以考虑将迭代返回的元素仅仅用于可以安全地重复执行多次的操作上。
2.如果一个元素是在迭代过程中被添加到数据集的, 又或者是在迭代过程中从数据集中被删除的, 那么这个元素可能会被返回, 也可能不会, 这是未定义的(undefined)。
3.元素如果在迭代过程中被删除了,可能不会被返回。
redis 127.0.0.1:6379> scan 0 match * count 1000
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"
redis 127.0.0.1:6379> scan 17 match * count 1000
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"
`