Facebook 的全球网络大揭秘:构建社交帝国的科技奇迹

Facebook 的全球网络大揭秘:构建社交帝国的科技奇迹
2023年10月15日 10:15 InfoQ

作者 | Engineer’s Codex

译者 | Sambodhi

策划 | 褚杏娟

在这篇文章中,我们将揭开 Facebook 如何构建并维护全球规模的社交网络,同时保持卓越性能和可用性的神秘面纱。Facebook 的成功不仅源于创新理念,还有一系列架构决策和性能优化。

首先,我们将了解 Facebook 如何通过分布式集群、区域扩展和全球覆盖满足数十亿用户需求。他们的架构将缓存与持久性存储系统分离,实现独立扩展。我们还将看到 Facebook 如何注重简单性,以便快速扩展、吸纳新工程师并保持高效流程。

此外,我们将研究 Facebook 在性能优化方面采取的策略,包括对 Memcached 的定制、失效守护进程 mcsqueal 的实现,以及其他关键技术如缓存、通信和故障处理。

最后,我们还将了解 Facebook 的教训和经验,包括如何处理全球规模的竞争条件、持续贡献开源社区以及在不断演进的技术领域中保持简单性的重要性。

如果你对构建和维护大规模分布式系统、性能优化和全球社交网络感兴趣,那么这篇文章将揭示 Facebook 的秘密,带你深入了解他们的成功之道。

Facebook 的数据库如同浩瀚星海,每秒钟接收数以亿计的请求,存储着海量的数据。传统 Web 架构已无法满足其巨大的需求。

Facebook 使用了一个称为 Memcached 的简单键值存储系统,并将其扩展以高效处理每秒数十亿次请求和数万亿的数据项。

回顾 2013 年,Facebook 自豪地宣布他们的系统已成为“全球最大的 Memcached 安装”。

如果你对技术细节不感兴趣,可以毫不犹豫地跳过前文,直接探寻文章末尾的“教训总结”部分。

Facebook 的扩展之道

  • 以用户为中心,任何更改都只能影响用户界面或运营问题。

  • 不要追求完美,即使这意味着可能会获取一些过时数据。

Facebook 的扩展策略

需求

Facebook 深知以下假设:

  1. 用户阅读多于写作。

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

  3. 需要迅速实现阅读、写入和通信。

于是,他们采用了三个扩展层次:集群、区域和全球。

他们扩展了 Memcached 以实现这一目标。

简要解释 Memcached

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

相较于数据库的高昂读取成本,内存的读取显得尤为昂贵。然而,Facebook 背负着数万亿的数据项,其数据库存储量可谓巨无霸级别。试想,在我开发的某款应用中,一个 1.2MB 的 JSON 响应竟需耗费约莫 1100 毫秒方能返回。然而,在 Memcached 的加持下,仅需 200 毫秒便可将其返回。

Facebook 在 Memcached 中存储的内容

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

倘若用户请求他们的个人资料信息,而且自上次请求以来没有更改,那个请求的响应已经存储在 Memcached 中。它还存储了常见的中间产物,例如来自 Facebook 机器学习算法的预计算结果。

为何钟情于 Memcached?

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

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

我发现他们的命名有点令人困惑,因此在本文的其余部分,我将称之为他们的分布式系统版,即 memcache。

集群扩展

目标:

  • 降低读取数据的延迟

  • 减轻读取数据对数据库的负载

在庞大的集群中,Facebook 坐拥成千上万台服务器。

每台服务器都有一个 Memcache 客户端,提供一系列功能(压缩、序列化、压缩等)。所有客户端都有一个包含所有可用服务器的映射。

加载一个热门的 Facebook 页面平均需要从 Memcache 中进行 521 次不同的读取操作

通常情况下,一个 Web 服务器必须与多位 Memcache 服务器互通有无,方能圆满处理一个请求。

降低延迟

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

Facebook 运用了三种策略:并行请求 + 批处理、更快捷的客户端 - 服务器通信和控制请求拥堵。

并行请求与批处理

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

他们创建了一个数据依赖性的 DAG,用于最大程度地提高一次可以获取的数据项数量,平均每次获取 24 个键。

优化客户端 - 服务器通信

他们将复杂性放在了一个无状态客户端中,以便保持 Memcache 服务器的简单性

对于从 Memcache 发起的获取操作,他们采用了 UDP,因为任何问题都会显示为客户端错误(因此用户只需重新尝试)。

而对于使用 mcrouter 实例的设置 / 删除操作,他们选择了 TCP 作为通信方式。

Mcrouter 犹如一位代理,为 Memcache 服务器提供接口,并将请求 / 响应路由至 / 自其他服务器。

管理拥塞

当众多请求同时涌来时,Memcache 客户端将运用滑动窗口机制,以控制未完成的请求数量。

他们确定了窗口大小,通过数据分析找到了用户延迟过高或同时到来的请求过多之间的黄金平衡点。

减轻数据库负担

Facebook 通过三种策略减轻了数据库的负载:租约、Memcache 池和池内复制。

租约

当客户端遭遇缓存未命中时,Memcache 实例会提供临时租约。时而,在服务器写回缓存之际,租约早已过期,意味着它已被更新的数据所取代。

租约化解了两大难题:

  • 过时的设置(Web 服务器在分布式 Memcached 中设置一个不正确的值)

  • 蜂拥而来的“兽群”(某个关键字有大量读写活动同时发生)

每个关键字每 10 秒仅分配一次租约。如此这般,峰值数据库查询速率从每秒 17000 次剧减至每秒 1300 次。

Memcache 池

一个庞大的 Memcache 服务器集群被划分为不同的池。

大多数关键字归属于默认池。其余的则归为“有问题”或“特殊”关键字之列,不在默认池中,例如频繁访问但缓存未命中不会对负载或延迟产生显著影响的关键字。

池内复制

对于一类关键字,我们选择在池内进行复制,原因包括:

(1)应用程序频繁同时获取多个关键字;

(2)整个数据集适合一个或两个 Memcache 服务器;

(3)请求速率远高于单个服务器的处理能力。详情参见第 3.2.3 节。

区域扩展

然而,我们无法无限扩展一个集群。

多个(前端)集群组成一个区域,它们共同承载着多个 Web 和 Memcache 服务器,以及一个存储集群。

在区域内扩展 Memcache 时,采用了三种策略:失效守护进程、区域池以及快速启动新集群的“冷启动”机制。

失效守护进程

失效守护进程(mcsqueal)如同一位守护者,它负责在一个区域内将缓存失效操作复制到所有缓存中。

当存储集群(数据库)中的数据发生变化时,失效守护进程会立即将失效操作发送到自身所在的集群。

每个数据库都有一个失效守护进程。

失效守护进程将删除操作分批成较少的数据包,然后将它们发送到每个前端集群中的 mcrouter 服务器,然后 mcrouter 服务器将失效操作路由到正确的 Memcache 服务器。

区域内的区域池

在区域内,所有前端集群共享着某些类型的 Memcache 服务器。这些服务器共同组成了一个区域池,为多个前端集群提供数据存储和访问服务。

由于复制操作的开销较大,区域池采用了一些策略来存储那些 “不常访问” 的数据。例如,根据用户的中位数数量、每秒读取次数以及中位数值大小等因素,来决定哪些数据应该被保留在区域池中。

冷集群准备

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

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

然而,或许会遇到一些缓存一致性的竞态条件。为了解决这个问题,他们巧妙地在冷集群的删除操作中添加了两秒的延迟。一旦冷集群的缓存命中率下降,这个延迟便会自动关闭。

全球扩展

将区域布局于世界各地,背后有着诸多种缘由:

  • 更贴近用户的距离;

  • 自然事件的缓解,如电力故障等;

  • 各地的经济激励措施,如低廉的电力和税收优惠等。

在各个区域之间,一个区域拥有一个存储集群和多个前端集群。

一个区域保存主数据库,而其他区域包含只读副本,这是通过使用 MySQL 的复制机制来实现的。

他们追求尽力而为的最终一致性,但强调性能和可用性。

在扩展到多个区域后,Facebook 的团队实施了失效守护进程 mcsqueal,以便能够立即编写代码来正确处理全球规模的竞态条件。

他们使用了一种远程标记机制,以降低读取过时数据的概率,特别是当写入发生在非主要区域时。你可以从论文中了解更多相关信息。

杂项性能优化

  • Facebook 对 Memcached 进行了精心调优:

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

    • 采用全局锁技术,使服务器多线程化;

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

  • 前两项优化成果已惠及开源社区。

  • Facebook 内部还有许多其他卓越优化措施。

  • 软件升级一组 Memcached 服务器所需时间超过 12 小时。

    • 他们对 Memcached 进行改造,将缓存值与其他数据结构存储在共享内存区域,以降低中断和停机的影响。

故障处理

在 Facebook 的巨大规模下,服务器、硬盘和其他硬件组件每分钟都面临故障的威胁。当主机不可用时,他们拥有一套自动化的修复系统。

为了替代一些故障服务器,他们将约 1% 的 Memcache 服务器纳入“Gutter 池”之中。

总体架构

教训总结

以下是 Facebook 总结的教训,供我们参考:

1. 分离缓存与持久性存储:将缓存和持久性存储系统分开,使我们能够独立地扩展它们。

2. 重视监控与运维效率:功能与性能同样重要,提高监控、调试和运维效率。

3. 无状态组件的优势:管理有状态组件比无状态组件更复杂。因此,将逻辑保持在无状态客户端有助于迭代功能并最小化中断。

4. 逐步发布与回滚:系统必须支持新功能的逐步发布和回滚,即使这可能导致特性集的暂时不一致。

5. 追求简单:简单性至关重要。

主要收获

  • 稳定性和可用性的重要性:Facebook 将稳定性和可用性放在首位,并且在权衡各种因素时进行深入的探讨、测量和充分解释。

  • 简单性的关键角色:简单性对于可扩展性和高效流程至关重要。它使 Facebook 能够快速扩展、吸纳新工程师并保持流程的顺畅。

  • 使用经过验证的技术:Facebook 长时间以来一直使用经过验证的技术,并根据需求定制了 Memcached,而不是立即构建自己的自定义键值存储。这种策略有助于招聘和引入新的工程师,因为大多数后端工程师都熟悉 Memcached。

  • 对研究社区的贡献:即使在当时,Facebook 也为研究社区做出了贡献。虽然现在这可能已经不那么新颖,但在当时这是一个相当大的贡献!

  • 支持开源文化:Facebook 积极支持开源文化,包括向开源 Memcached 贡献了一些修改,并在其他领域如 React、LLaMA、PyTorch 等方面持续支持。尽管有关 TAO 论文描述了一个自定义构建的系统替代了部分功能,但不确定 Facebook 是否仍在使用 Memcached。

原文链接

https://engineercodex.substack.com/p/how-facebook-scaled-memcached#circle=on

声明:本文为 InfoQ 翻译,未经许可禁止转载。

财经自媒体联盟更多自媒体作者

新浪首页 语音播报 相关新闻 返回顶部