Facebook 如何使用 Memcached 每秒处理数十亿请求

Facebook 每秒接收数十亿个请求并在其数据库中存储数万亿个项目。

据他们的团队称,“传统的网络架构”根本无法满足社交网络的需求。

Facebook 使用了一种简单的键值存储,称为 Memcached将其缩放至 每秒高效处理数十亿个请求 涵盖数万亿件物品。

他们指出 2013年 他们的系统是“最大的 memcached 全球安装量”。

如果您想跳过技术部分,请直接跳至 经验教训和总结部分 在最后。

  • 以用户为中心。 任何改变必须 仅有的 影响用户面临或操作的问题。

  • 不要追求完美。 如果这意味着他们可以进一步扩大规模,那么他们也不介意一些用户获取过时的数据。

他们牢记以下假设:

  1. 用户阅读的多于写的多。

  2. 有多个数据源可供读取。

  3. 他们需要几乎立即进行阅读、写作和交流。

它们有 3 个扩展层: 集群、区域和全球。

他们扩大了规模 Memcached 去做这个。

它是 基本键值存储,使用哈希表实现,存储在内存中。它是数据库上的缓存层

数据库读取比内存读取要昂贵得多。Facebook 的数据库存储了数万亿个项目。

例如,在我开发的一款应用中,1.2MB 的 JSON 响应需要大约 1100 毫秒才能返回。在我安装 Memcached,缓存后仅需200ms即可返回。

Memcached 存储对各种请求的响应。

如果用户请求他们的个人资料信息,并且自上次请求以来没有发生变化,则该请求的响应已存储在 Memcached 中。

它还存储了常见的中间产物,例如 Facebook 机器学习算法的预先计算的结果。

“Memcached 提供了一组简单的操作(设置、获取和删除),这使其成为大规模分布式系统中的基本组件。” (第 2 节,概述)

在论文中,他们使用 Memcached 作为基本的键值存储,以及 内存缓存 作为分布式系统版本 Memcached 他们正在运行。这个分布式版本 Memcached 具有额外的功能,例如用于服务器到服务器通信的特殊客户端等等。

我发现它们的命名令人困惑,所以我将参考它们的分布式系统版本, 内存缓存,此帖的剩余部分。

目标:

在一个集群内,Facebook 拥有数千台服务器。

每个服务器都有一个 内存缓存 客户端,提供一系列功能(压缩、序列化、压缩等)。所有客户端都有所有可用服务器的地图。

加载一个受欢迎的 Facebook 页面平均会导致 521 条不同读段来自 内存缓存

一个 Web 服务器通常必须与多个 内存缓存 服务器只处理 1 个请求。

请求必须以近乎实时的方式完成并返回。

Facebook 有三种策略:并行请求 + 批处理、更快的客户端-服务器通信以及控制请求拥塞。

使用并行请求和批处理的目的是减少网络往返次数。

他们创建了一个数据依赖关系 DAG,用于最大化每次可获取的项目数量,即每次 24 个键(平均)。

复杂性被放入无状态客户端,以便 内存缓存 服务器可以保持简单。

UDP 用于获取请求 内存缓存 因为任何问题都会显示为客户端错误。(所以用户只需再试一次)

TCP 用于设置/删除操作,使用 微型路由器 实例。

微路由器 是一个代理,它呈现 内存缓存 服务器接口并将请求/回复路由到其他服务器/从其他服务器路由请求/回复。

当同时有太多请求时, 内存缓存 客户端将使用滑动窗口机制来控制未完成请求的数量。

他们使用的窗口大小是通过一些数据分析得出的,他们在过高的用户延迟和过多的传入请求之间找到了平衡

Facebook 通过三种策略减少了数据库的负载:租赁、 内存缓存 池以及池内的复制。

当客户端遇到缓存未命中时, 内存缓存 实例给出了一个 临时租约。有时,当服务器写回缓存时,租约已经过期,这意味着它已经太旧了并且已经被更新的数据填充。

租赁解决了两个问题:

每个密钥每 10 秒只能发出一次租约。

租赁使峰值数据库查询率从 17K/s 降至 1.3K/s。

集群的 内存缓存 服务器被划分为不同的池。

大多数密钥都有一个默认池。

其余的具有不在默认池中的“有问题的”或“特殊”的键,例如经常访问但缓存未命中与负载或延迟无关的键。

在以下情况下,我们选择复制池中的一类密钥:

(1)应用程序经常同时获取许多密钥

(2)整个数据集适合一个或两个 内存缓存 服务器

(3)请求率远远高于单个服务器可以管理的速率。

引自第 3.2.3 节

您无法无限地扩展集群。

多个(前端)集群组成一个区域。它们将拥有多个 Web 和 内存缓存 服务器以及存储集群。

他们有三个扩展策略 内存缓存 在一个区域内:一个无效守护进程,一个区域池和一个“冷启动”机制,以快速启动新的集群。

一个失效守护进程(称为 麦斯奎尔) 将缓存失效复制到区域内的所有缓存中。

当存储集群(数据库)中的数据发生变化时,它会向其自己的集群发送失效通知。

每个数据库都有 麦斯奎尔

麦克斯奎尔 将删除批量拆分成更少的数据包,然后将其发送到 微型路由器 每个前端集群中的服务器,然后将失效路由到右侧 内存缓存 服务器。

区域池 内存缓存 一个区域内的所有集群共享某些类型数据的服务器。

多个前端集群共享同一组 内存缓存 位于某个区域内的服务器。

复制成本高昂,因此区域池会存储“奇怪”的数据,例如不经常访问的数据。这是通过使用一些因素(例如用户中位数、每秒获取次数和中值大小)来实现的。

“冷集群”或具有空缓存的前端集群从“温集群”或具有正常命中率缓存的集群检索数据。

这将新集群的“启动时间”从几天缩短到仅仅几个小时。

注意:此处可能存在与缓存一致性相关的竞争条件,它们通过在冷集群中添加两秒的删除延迟来解决此问题。一旦冷集群的缓存命中率降低,此功能就会关闭。

出于多种原因,将区域划分在世界各地:

  • 更贴近用户

  • 减轻停电等自然事件的影响

  • 一些地区有更好的经济激励(更便宜的电力、减税等)。

跨地域,一个地域有一个存储集群和多个前端集群。

一个区域保存主数据库,其他区域包含只读副本,这是使用 MySQL 的复制机制完成的。

他们尽最大努力实现最终一致性,但强调性能和正常运行时间。

Facebook 团队实现了失效守护进程 麦斯奎尔 在扩展到多个区域之后,他们可以立即编写代码来正确处理行星规模的竞争条件。

他们使用远程标记机制来降低读取陈旧数据的概率,当从非主区域进行写入时,这种概率更高。请从论文中了解更多相关信息。

  • 他们进行了优化 memcached 本身:

    • 允许自动扩展内部哈希表

    • 使用全局锁使服务器实现多线程

    • 为每个线程分配自己的 UDP 端口

  • 前 2 项优化回馈给了开源社区

  • 他们还进行了许多仅保留在 Facebook 内部的优化。

  • 软件升级到一套 memcached 服务器可以接管 12小时

在 Facebook 的规模下,他们的服务器、硬盘和其他硬件组件每分钟都会出现故障。

当主机无法访问时,他们有一个自动补救系统。

约 1% 内存缓存 集群中的服务器是 排水沟池,用于替代一些发生故障的服务器。

首先,Facebook 自己提供的教训,可以在第 9 节(结论)中找到。

(1)分离缓存和持久存储系统使我们能够独立地扩展它们。

(2)提高监控、调试和运行效率的功能与性能同样重要。

(3)管理有状态组件在操作上比无状态组件更复杂。因此,将逻辑保留在无状态客户端中有助于迭代功能并最大限度地减少中断。

(4)系统必须支持新功能的逐步推出和回滚,即使这会导致功能集的暂时异构。

(5) 简单至关重要。

  • 他们优先考虑 正常运行时间和可用性。 他们采取的每项权衡都经过了探索、衡量和很好的解释。

  • 简单至关重要 这也是 Instagram 工程的指导原则 随着他们的扩展,他们也开始采用自己的架构。

  • 他们 采用成熟的技术进行扩展 很长一段时间。我注意到他们定制 memcached 以满足他们的需求,而不是立即构建自己的定制 KV 商店。

    • 我不相信他们使用 memcached 不再以这种方式,但有趣的是,这似乎是一个 与谷歌或苹果截然不同,他们更喜欢在内部开发自己的定制工具

    • 我想知道这是否有助于入职和招聘。大多数后端工程师都熟悉 memcached,因此很容易理解它的工作原理。

    • 这也是 Instagram 的指导原则。

  • 即使在那时, 他们为研究界做出了贡献。这可能现在不是什么新鲜事,但在当时这可是相当大的贡献!

  • Facebook 还将他们的一些变化贡献给了开源 memcached当时,开源文化在 Facebook 上盛行,并且现在它依然很强大,拥有 React、LLaMA、PyTorch 等等。

我不知道 Facebook 是否还在使用 Memcached,因为这篇论文 TAO 描述了一个替代其部分内容的定制系统。

来源: Facebook 的 Memcache 扩展 (2013)

Leave a Reply

Your email address will not be published. Required fields are marked *

近期新闻​

编辑精选​