尽管出现了一些引起相当多麻烦的挫折,但确实存在解决方案,而且可能比预期的更简单。
本系列文章将重点介绍使用 Redis 时出现的一些最令人头疼的问题,以及如何解决这些问题的技巧。这些内容基于我们运行数千个 Redis 数据库实例的实际经验。
我们之前在本系列文章中讨论了 Redis 的复制 缓冲区 和 超时。在本篇文章中,我们将为您介绍 Redis 维护的另一种类型的缓冲区,即客户端缓冲区。在某些情况下,如果这个缓冲区不受控制,可能会成为许多麻烦的根源。
您可能已经知道 Redis 是一个内存数据库,这意味着所有数据都直接在 RAM 中进行管理和服务。这使得 Redis 能够提供无与伦比的性能,在亚毫秒级的延迟下处理数十万甚至数百万个请求。RAM 是目前技术提供的最快的存储方式。为了了解延迟数字,请查看以下内容
Latency Comparison Numbers (~2012)
----------------------------------
L1 cache reference 0.5 ns
Branch mispredict 5 ns
L2 cache reference 7 ns 14x L1 cache
Mutex lock/unlock 25 ns
Main memory reference 100 ns 20x L2 cache, 200x L1 cache
Compress 1K bytes with Zippy 3,000 ns 3 us
Send 1K bytes over 1 Gbps network 10,000 ns 10 us
Read 4K randomly from SSD* 150,000 ns 150 us ~1GB/sec SSD
Read 1 MB sequentially from memory 250,000 ns 250 us
Round trip within same datacenter 500,000 ns 500 us
Read 1 MB sequentially from SSD* 1,000,000 ns 1,000 us 1 ms ~1GB/sec SSD, 4X memory
Disk seek 10,000,000 ns 10,000 us 10 ms 20x datacenter roundtrip
Read 1 MB sequentially from disk 20,000,000 ns 20,000 us 20 ms 80x memory, 20X SSD
Send packet CA->Netherlands->CA 150,000,000 ns 150,000 us 150 ms
Notes
-----
1 ns = 10^-9 seconds
1 us = 10^-6 seconds = 1,000 ns
1 ms = 10^-3 seconds = 1,000 us = 1,000,000 ns
Credit
------
By Jeff Dean: https://research.google.com/people/jeff/
Originally by Peter Norvig: https://norvig.com/21-days.html#answers
Contributions
-------------
'Humanized' comparison: https://gist.github.com/hellerbarde/2843375
Visual comparison chart: https://i.imgur.com/k0t1e.png
Redis 顾名思义,是一个远程服务器,这意味着客户端(通常)通过网络连接到它。因此,客户端请求返回到客户端所需的时间将远远超过 Redis CPU 从 RAM 中实际获取数据的时间。这种数量级的差异的直接含义是,如果 Redis 没有客户端缓冲区,它将一直忙于处理请求,直到该时间结束。
客户端缓冲区构成一个内存空间,用于服务客户端请求,并且每个与 Redis 的连接都分配有自己的缓冲区空间。在处理请求后,Redis 将响应数据复制到客户端缓冲区,并继续处理后续请求,而请求的客户端则以其自身网络决定的速度通过该连接读取数据。Redis 的客户端缓冲区在 redis.conf 文件中通过 client-output-buffer-limit normal 指令进行配置(您可以在运行时使用 config get client-output-buffer-limit 获取此设置)。默认的 redis.conf 文件定义如下
client-output-buffer-limit normal 0 0 0
这些值分别表示缓冲区的软限制、硬限制和超时时间(秒)(类似于 复制缓冲区 的行为)。它们用作保护,当缓冲区的大小达到以下条件时,Redis 会终止连接,而不允许客户端读取回复:a) 达到软限制并保持该状态,直到超时时间过期或 b) 达到硬限制。将这些限制设置为 0 表示禁用该保护。
但是,与复制缓冲区不同,客户端缓冲区的内存分配来自 Redis 的数据内存空间。Redis 可以使用的总内存量由 maxmemory 指令设置,一旦达到该限制,Redis 将采用其配置的驱逐策略(由 maxmemory-policy 指令定义)。这实际上意味着,性能缓慢的客户端和/或大量并发连接可能会导致您的 Redis 实例过早地驱逐键,或因内存不足消息 (OOM) 而拒绝更新,因为其内存使用量(主要是数据集大小和客户端缓冲区的总和)已达到内存限制。
由于生活的相对性,客户端并不一定需要很慢才能触发此行为。由于访问 RAM 和从网络读取之间的速度差异巨大,即使使用性能最高的客户端和网络链路,也很容易用膨胀的客户端缓冲区耗尽 Redis 的内存。例如,考虑(邪恶的)KEYS 命令,一旦发出,Redis 将将整个键命名空间复制到客户端缓冲区。如果您的数据库包含大量键,这将足以触发驱逐。
警告:谨慎使用 KEYS,并且不要在生产环境中使用。除了可能触发上述驱逐之外,使用它还会导致 Redis 被阻塞很长时间。
KEYS 并不是唯一可能导致这种情况的命令。同样,Redis 的 SMEMBERS、HGETALL、LRANGE 和 ZRANGE(以及关联的命令)如果您的值(和范围)足够大,或者由于每个连接都需要单独的缓冲区,如果您有多个打开的连接,则可能会产生相同的效果。
因此,强烈建议避免不负责任地使用这些命令。在它们的位置上,首选 SCAN 命令族,该命令族自 v2.8 版本起可用。这些命令不仅允许 Redis 在后续 SCAN 调用之间继续处理请求,而且还可以降低耗尽客户端缓冲区的可能性。
客户端缓冲区是 Redis 内存需求和管理方面经常被忽视的一个方面。它们默认情况下处于禁用状态,风险很大,因为这会导致内存不足。通过相应地设置缓冲区的阈值,同时考虑 "maxmemory" 设置以及现有的和预测的内存使用量和应用程序的流量模式。负责任地使用上述命令可以避免令人不愉快的状况,这些状况会导致您头痛。敬请期待我们 DevOps 面临的 Redis 难题系列的下一篇文章!