dot 快速的未来将在您的城市举办的活动中到来。

加入我们在 Redis 发布会

DevOps 面临的 Redis 难题:客户端缓冲区

Redis 提供了各种工具,旨在提高和维护高效的内存数据库使用。虽然其独特的数据类型和命令可以对数据库进行微调,以便在应用程序级别无需任何额外处理的情况下为应用程序请求提供服务,但错误配置或更确切地说,使用开箱即用的配置,会导致操作挑战和性能问题。

尽管出现了一些引起相当多麻烦的挫折,但确实存在解决方案,而且可能比预期的更简单。

本系列文章将重点介绍使用 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 的 SMEMBERSHGETALLLRANGEZRANGE(以及关联的命令)如果您的值(和范围)足够大,或者由于每个连接都需要单独的缓冲区,如果您有多个打开的连接,则可能会产生相同的效果。

因此,强烈建议避免不负责任地使用这些命令。在它们的位置上,首选 SCAN 命令族,该命令族自 v2.8 版本起可用。这些命令不仅允许 Redis 在后续 SCAN 调用之间继续处理请求,而且还可以降低耗尽客户端缓冲区的可能性。

客户端缓冲区是 Redis 内存需求和管理方面经常被忽视的一个方面。它们默认情况下处于禁用状态,风险很大,因为这会导致内存不足。通过相应地设置缓冲区的阈值,同时考虑 "maxmemory" 设置以及现有的和预测的内存使用量和应用程序的流量模式。负责任地使用上述命令可以避免令人不愉快的状况,这些状况会导致您头痛。敬请期待我们 DevOps 面临的 Redis 难题系列的下一篇文章!