尽管存在一些挫折,这些挫折造成了不少麻烦,但解决方案确实存在,甚至可能比预期的更简单。
本系列文章将重点介绍使用 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: http://research.google.com/people/jeff/
Originally by Peter Norvig: http://norvig.com/21-days.html#answers
Contributions
-------------
'Humanized' comparison: https://gist.github.com/hellerbarde/2843375
Visual comparison chart: http://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
这些值分别代表缓冲区的软限制、硬限制和超时(以秒为单位)(类似于 复制缓冲区的行为)。 它们起到保护作用,当缓冲区的大小达到 a) 软限制并保持到超时到期或 b) 硬限制时,Redis 将终止连接 - 而不允许客户端读取回复。 将这些限制设置为 0 意味着禁用该保护。
但是,与复制缓冲区不同,客户端缓冲区的内存分配是从 Redis 的数据内存空间中获取的。 Redis 可以使用的总内存量由 maxmemory 指令设置,一旦达到,Redis 将采用其配置的逐出策略(由 maxmemory-policy 指令定义)。 这实际上意味着,性能低下的客户端和/或大量并发连接可能会导致您的 Redis 实例过早地逐出密钥,或者由于其内存使用量(主要是数据集的大小和客户端缓冲区的总和)已达到内存限制,因此拒绝使用内存不足消息 (OOM) 进行更新。
由于生活的相对性,客户端不一定必须很慢才能触发此行为。 由于访问 RAM 和从网络读取之间的速度差异巨大,因此即使使用性能最佳的客户端和网络链接,通过膨胀的客户端缓冲区耗尽 Redis 的内存实际上也很容易实现。 例如,考虑(邪恶的)KEYS 命令,一旦发出,Redis 会将整个密钥命名空间复制到客户端缓冲区。 如果您的数据库具有大量密钥,仅此一项就足以触发逐出。
警告:使用 KEYS 时要格外小心,切勿在生产环境中使用。 除了触发上述逐出的可能性之外,使用它您可能会冒着在很长一段时间内阻止 Redis 的风险。
但是,KEYS 不是唯一会导致这种情况的命令。 同样,如果您的值(和范围)足够大,或者由于每个连接都需要一个单独的缓冲区,如果您有多个打开的连接,Redis 的 SMEMBERS、 HGETALL、LRANGE 和 ZRANGE(和相关命令)可能会产生相同的效果。
因此,强烈建议不要不负责任地使用这些命令。 相反,首选自 v2.8 以来可用的 SCAN 系列命令。 这些命令不仅允许 Redis 在后续 SCAN 调用之间继续处理请求,而且还降低了耗尽客户端缓冲区的机会。
客户端缓冲区是 Redis 内存需求和管理中经常被忽视的一个方面。 它们的默认设置(即禁用)非常危险,因为它可能很快导致内存耗尽。 通过相应地设置缓冲区的阈值 - 考虑到“maxmemory”设置、现有和预测的内存使用情况以及应用程序的流量模式。 负责任地使用上述命令可以防止令人不快的意外情况,这些情况会让您头痛不已。 请继续关注我们 Devops 的 Redis 常见难题的下一期!