为什么建议不要在Redis中使用KEYS?

时间:2017-01-09 06:16:26

标签: redis

在Redis中,建议不要使用{{3}}。为什么会这样?是因为它的时间复杂度是O(N)吗?或其他原因。

2 个答案:

答案 0 :(得分:1)

时间复杂性非常糟糕。请注意N中的O(N)是指数据库中的键总数,而不是过滤器模式选择的键数。因此,对于生产数据库来说,这可能是一个非常大的数字。

更糟糕的是,由于只有一个命令可以同时运行(Redis不是多线程的),所以其他一切都必须等待该KEYS完成。

答案 1 :(得分:0)

我做了以下实验来证明KEYS命令有多危险。

当一个带有 KEYS 的命令运行时,其他 KEYS 命令正在等待运行时间。 KEYS命令的一次运行有2个阶段,第一个是从Redis获取信息,第二个是发送给客户端。

$ time src/redis-cli keys "*" | wc -l
1450832
real    0m17.943s
user    0m8.341s


$ src/redis-cli
127.0.0.1:6379> slowlog get
1) 1) (integer) 0
   2) (integer) 1621437661
   3) (integer) 8321405
   4) 1) "keys"
      2) "*"

因此,它在 Redis 上运行了 8 秒,然后通过管道传输到“wc”命令。 Redis 在 8 秒内完成了这个命令,但 'wc' 命令需要 17 秒的数据来完成计算。所以内存缓冲区必须至少存在 17 秒。现在,让我们想象一下网络上的客户端,这些数据也必须传送到客户端。如果我们有 10 个键命令,它们将一个一个地在 Redis 上运行,当第一个命令完成和下一个命令运行时,第一个命令的结果必须存储在内存中,然后客户端才会使用它们。这一切都需要内存,所以我可以想象这样一种情况,第 5 个客户端正在运行 KEYS 命令,但我们仍然需要保留第一个客户端的数据,因为它们仍然没有通过网络传输。

让我们测试一下。

场景:让我们有一个 200M 大小(1000M 物理内存)的 Redis DB,并检查一次执行 KEYS 需要多少内存,以及通过网络完成需要多长时间。然后模拟运行5个相同的KEYS命令,看是否杀死Redis。

$ src/redis-cli info memory
used_memory_human:214.17M
total_system_memory_human:926.08M

When run from the same node:
$ time src/redis-cli keys "*" | wc -l
1450832
real    0m17.702s
user    0m8.278s

$ free -m
              total        used        free      shared  buff/cache   available
Mem:            926         301         236          24         388         542
Mem:            926         336         200          24         388         507
Mem:            926         368         168          24         388         475
Mem:            926         445          91          24         388         398
Mem:            926         480          52          24         393         363
Mem:            926         491          35          24         399         352
-> looks like it consumed 190M for the KEYS command

-> 所以,Redis忙了8s的命令,但是这个命令消耗了17s的内存。 -> 只运行一个KEYS命令只会阻塞Redis 8s,但不会导致OOM

让我们(几乎)同时运行 2 个 KEYS 命令(无论如何都会一个接一个地运行)

$ time src/redis-cli keys "*" | wc -l &
$ time src/redis-cli keys "*" | wc -l &

$ free -m
              total        used        free      shared  buff/cache   available
Mem:            926         300         430          24         194         546
Mem:            926         370         361          24         194         477
Mem:            926         454         276          24         194         393
Mem:            926         589         141          24         194         258
Mem:            926         693          37          24         194         154
-> now we used 392M memory for 26s, while Redis is hung for 17s
-> but we still have a running Redis

让我们(几乎)同时运行 3 个 KEYS 命令(无论如何都会一个接一个地运行)

$ time src/redis-cli keys "*" | wc -l &
$ time src/redis-cli keys "*" | wc -l &
$ time src/redis-cli keys "*" | wc -l &

$ free -m
              total        used        free      shared  buff/cache   available
Mem:            926         299         474          23         152         549
Mem:            926         385         388          23         152         463
Mem:            926         512         261          23         152         336
Mem:            926         573         200          23         152         275
Mem:            926         711          61          23         152         136
Mem:            926         842          21          21          62          17
-> now we used 532M memory for 36s, while Redis is hung for 26s
-> but we still have a running Redis

Let's run 4 KEYS commands at the (almost) same time (that will run one after another anyway)
$ time src/redis-cli keys "*" | wc -l &
$ time src/redis-cli keys "*" | wc -l &
$ time src/redis-cli keys "*" | wc -l &
$ time src/redis-cli keys "*" | wc -l &
-> that kills Redis

Redis 日志中没有任何内容:

2251:C 19 May 16:03:05.355 * DB saved on disk
2251:C 19 May 16:03:05.379 * RDB: 2 MB of memory used by copy-on-write
1853:M 19 May 16:03:05.432 * Background saving terminated with success

在/var/log/messages

May 19 16:08:01 consumer2 kernel: [454881.744017] redis-cli invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE), nodemask=(null), order=0, oom_score_adj=0
May 19 16:08:01 consumer2 kernel: [454881.744180] [<8023bdb8>] (oom_kill_process) from [<8023c6e8>] (out_of_memory+0x134/0x36c)

结论:

  • 我们可以杀死健康的 Redis 实例,消耗 200M 的 RAM,其中 70% 的 RAM 在操作系统上可用,只需运行一个接一个发出的 4 个 KEYS 命令并一个接一个运行。只是因为即使在 Redis 执行完结果后也必须缓冲结果。
  • 无法使用 maxmemory 保护 Redis 免受该行为的影响,因为内存使用不是 SET 命令的结果