【问题标题】:Why KEYS is advised not to be used in Redis?为什么不建议在 Redis 中使用 KEYS?
【发布时间】:2021-05-20 04:32:58
【问题描述】:

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

【问题讨论】:

标签: redis


【解决方案1】:

是的。

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

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

【讨论】:

  • 好的。但是即使是 O(N),也可以使用 SMEMBERS 命令。因为这里 N 只是集合中的键数(不是整个数据库),N 的最大值可以是 2^32-1,这不是问题。我说的对吗?
  • 是的,除非该集合也很大(您需要在达到 2^32 个集合成员之前重新考虑设计)。
  • 谢谢。您是说 redis 一次只能执行一个命令,因为它是单线程的。但是通过时间多路复用它不会同时处理其他命令吗?因此,其他命令不必等到 KEYS 命令完成。请参考:stackoverflow.com/questions/10489298/…
  • 此外,您所说的问题(等到命令完成)只会在 EVAL/运行 Lua 脚本的情况下发生,我猜。
  • 我认为您误解了链接的文章。所有 Redis 命令都是按顺序执行的。 stackoverflow.com/a/17099452/14955
【解决方案2】:

我做了以下实验来证明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 在 OS 上只需要运行 4 个 KEYS 命令一个接一个地发出并一个接一个地运行。只是因为即使在 Redis 执行完结果之后也必须缓冲结果。
  • 无法使用 maxmemory 保护 Redis 免受该行为的影响,因为内存使用不是 SET 命令的结果

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-10-02
    • 1970-01-01
    • 1970-01-01
    • 2021-05-14
    • 2013-07-24
    • 2012-04-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多