Redis 的复制是一个不可或缺的工具——它既可以用来提高 Redis 设置的可用性(您可以在 这篇文章 中阅读更多关于 Redis 可用性的内容),也可以用来通过对只读从机进行读操作来扩展 Redis。
在实施复制时,Redis 利用其核心功能——即 RDB 文件——提供了一种简单而优雅的机制,该机制在大多数情况下都是有效的。复制被广泛采用,包括在我们自己的 Redis 云服务中,并且是 Redis 的另一个有用且经过验证的功能。
但是,在某些情况下,激活 Redis 的复制可能并非易事。这些通常是罕见且极端的场景,例如当您的数据集大小显着增长时。但在深入探讨这些细节之前,让我们先了解一下 Redis 复制的工作原理。
“在一个遥远的银河系中”
Redis 中的复制就像从机(或学徒)与其主机的对话。
这种对话遵循以下思路
上述交换本质上允许从机在两个阶段同步主机数据集的内容:首先,复制完整的,尽管稍微过时的数据主体;然后,在更短的追赶期间应用一个仅包含更新的局部子集。
如上所述,某些数据库(取决于其使用目的和用途)的大小会显着增长。增长可能是突然发生的,也可能随着时间的推移逐渐发生,但无论如何,事实仍然是——您的数据库已经变得相当大。而更大并不总是更好,尤其是在尝试引导复制从机时。
当从机尝试从管理着大型数据集(大约 25GB 或更大)的主机同步时,会遇到几个问题。首先,主机服务器可能需要大量的 RAM,甚至高达数据库大小的 3 倍,才能进行快照。虽然对于小型数据库来说也是如此,但随着数据库的增长,这项要求变得越来越难以满足。其次,数据集越大,用于快照目的复制另一个进程就越耗时且越困难,这直接影响主服务器进程。
这种现象被称为“由于 fork 导致的延迟”,并在 这里 和 redis.io 上进行了说明。但是,让我们假设后者不是问题,并且通过投入足够的硬件,您已经为主机服务器提供了足够的资源,以便创建快照和 fork 延迟可以忽略不计。请记住,在完成所有 fork 操作后,需要将文件从主机复制到从机。
遗憾的是,这是通过与客户端用来从数据库访问数据库的相同互连进行的。大型数据库在大多数情况下由许多生成大量流量的客户端使用。此外,在云设置中,相同的网络也可能用于访问类似 EBS 的网络附加存储。将 10GB 的文件传输流量添加到该传输介质中几乎不会减少任何现有的拥塞。实际上,恰恰相反。即使假设存在最佳网络条件,仍然存在关于“胖乎乎的”RDB 文件能够通过电线传播并写入本地磁盘的速度的物理限制。
底线是,考虑到这些因素以及它们所产生的复合效应,从机需要时间才能准备好并准备好文件以供加载。并且一旦到位,从机也需要时间来加载文件。您不需要详细的模型或复杂的数学证明来直观地理解一个事实,即数据集越大,复制、转储、复制和加载到从机中所需的时间就越长。
“但这又怎样呢?”你可能会说,“我并不需要每天设置一个新的从机。我有时间,我可以等。”“你必须忘记你已经学到的东西”,你将无休止地等待。
从机永远无法完成同步,并且复制将不会开始。这是因为在创建快照、传输快照并将其加载到从机中的过程中,主机一直在忙于处理请求(在大型繁忙的数据库中,可能处理了大量请求)。更新在专用的复制缓冲区中累积,但该缓冲区的大小最终是有限的,并且一旦耗尽,它就无法再用于将从机更新到最新状态。
如果没有有效的更新缓冲区来追赶,从机就无法完成必要的初步同步循环,从而实时地开始从主机复制更新。为了纠正这种情况,Redis 在这些情况下会重新启动从机的同步过程。
因此,学徒又回到了起点,忘记了之前学到的所有东西,并带着一个请求回到了主机:“我想学习,成为像您一样的人。”然而,由于基本情况保持不变,后续尝试启动复制很可能与最初的迭代一样注定要失败。
这种情况虽然罕见,但确实是存在的,正如 Manohar 在 这里 提出的那样。即将发布的 Redis v2.8 肯定会对此进行改进,在将来,开源社区几乎可以肯定能完全克服这个问题。同时,如果您正在寻找立竿见影的解决方案,您可以访问我们的 github 下载我们版本的 Redis 2.6.14。在这个版本中,我们包含了一个客户端节流机制,以谨慎地为从机赢得足够的时间来完成同步。我们的节流机制通过在主机服务器对应用程序客户端请求的响应中引入延迟来工作。虽然乍一看似乎违反直觉,但额外的延迟为从机提供了足够的“喘息空间”来完成传输并重播更新日志,然后再者耗尽空间,从而允许同步完成并开始复制。
在实施这种机制时,我们添加了新的配置变量
slave-output-buffer-throttling
该变量使用以下语法设置
CONFIG SET slave-output-buffer-throttling <low> <high> <rate> <delay>
其中
因此,例如,以下设置
CONFIG SET slave-output-buffer-throttling 32768 131072 102400 1000
会导致复制过程按以下方式进行,但会有一些更改
主机对完成复制循环所需时间的估计方式如下
在我们的示例中,假设数据集的大小为 25GB。鉴于我们设置的速率为 100MB/s(或 102400 字节/秒)
slave-output-buffer-throttling
主服务器将估计复制周期将在 250 秒内完成(= 25GB / 100MB/s)。
然后,如果复制缓冲区增长过快,主服务器将通过延迟响应来启动流量控制。 `<high>` 参数确定复制过程结束时允许的最大缓冲区大小,因此流量控制会在任何时候被触发并按比例应用。这意味着,例如,在循环开始后的 125 秒,主服务器将假设它已经完成了 50%。此时,如果复制缓冲区的大小超过 64MB(这是 `<high>` 131072 字节值的 50%),主服务器将应用延迟。
引入的实际延迟本身与缓冲区超出限制的程度成正比,并且不会超过 `<delay>` 设置的 1000 毫秒,以保持服务器的响应能力。出于同样的原因,服务器永远不会限制新连接的第一个请求。
最后,
slave-output-buffer-throttling
以及标准 Redis
client-output-buffer-limit
(了解更多信息 这里)机制可以结合使用,因此您需要确保它们不会冲突。您可以通过设置
client-output-buffer-limit
高于
slave-output-buffer-throttling
例如
CONFIG SET client-output-buffer-limit 262144 131072 60
在此示例中,如果流量控制无法抑制缓冲区的大小(可能是由于创建大型键的请求导致的),那么标准
client-output-buffer-limit
机制将在缓冲区达到 256MB 或在超过 `<high>` 128MB 限制超过 60 秒时启动并中断循环。
我们希望这些解释和解决方案对你们中的一些人有用!愿原力与你们同在。