5 小时 CDN

2021-08-05 20:57:00

术语“CDN”(“内容交付网络”)让人联想到管理大量硬件的谷歌规模的公司,每秒处理数百千兆比特。但是 CDN 只是 Web 应用程序。这不是我们倾向于认为它们的方式,但这就是它们的全部。当您坐在咖啡店时,您可以在一台使用了 8 年的笔记本电脑上构建功能性 CDN。我将讨论如果您在接下来的五个小时内构建 CDN,您可能会想到什么。准确定义 CDN 的功能很有用。 CDN 将来自中央存储库(称为源)的文件缓存起来,并将副本存储在靠近用户的位置。在黑暗时代,起源是 CDN 的 FTP 服务器。如今,起源只是网络应用程序,CDN 充当代理服务器。这就是我们正在构建的:分布式缓存代理。 HTTP 定义了一个复杂而繁琐的缓存特性的整个基础设施。这一切都非常令人生畏和复杂。因此,我们将抵制从头开始构建并使用其他人为我们所做的工作的冲动。我们有选择。我们可以使用 Varnish(脚本编写!边缘包括!PHK 博客文章!)。我们可以使用 Apache Traffic Server(这是今年唯一使用 ATS 的新团队!)。或者我们可以使用 NGINX(我们已经在运行它!)。唯一可以肯定的是,你会讨厌你选择的任何一个。全部尝试并选择您最不讨厌的一个。我们谈论的建筑不是基本的。但也没有那么糟糕。我们所要做的就是采用我们古老的 Rails 设置并在多个城市运行它。如果我们能弄清楚如何让澳大利亚的人们访问我们在悉尼的服务器,以及如何让智利的人们访问我们在圣地亚哥的服务器,我们就会有一些我们可以合理地称之为 CDN 的东西。任播:获取可路由的地址块,用 BGP4 在多个地方发布它们,然后在 Twitter 上假装您对“社区”和“路由反射器”有意见。让互联网为你做路由。缺点:更难做,而且互联网有时是垃圾。好处:你可能会变得难以忍受。 DNS:运行特技 DNS 服务器,根据 IP 地理位置返回特定服务器地址。缺点:互联网正在远离可地理定位的 DNS 源地址。好处:您可以在没有帮助的情况下将其部署到任何地方。

像游戏服务器一样:Ping一堆服务器并使用最好的。缺点:必须拥有客户。好处:没关系,因为您不拥有客户。您可能会使用一点(1)和一点(2)。 DNS 负载平衡非常简单。您甚至不必自己构建它;您可以在 DNSimple 等公司上托管 DNS,然后定义返回地址的规则。就行了!任播更难。关于这一点,我们还有更多话要说——但不是在这里。同时,您可以使用我们,并在大约 2 分钟内部署一个带有 Anycast 地址的应用程序。这是偏见。但也:真实。繁荣,CDN。在一堆城市中的每一个中放置一个 NGINX,运行 DNS 或 Anycast 来确定流量方向,你就完成了 90%。剩下的 10% 需要几个月的时间。海水深处布满海底电缆,不断向附近的船只喊叫:“驶过我”!土地也好不到哪里去,正如旧的网络人棚屋所说:“反铲,反铲,深挖——让骨干进入睡眠状态”。当您在单个位置运行服务器时,您不会注意到这一点。运行两台服务器,你会开始注意到。在世界各地运行服务器,你会注意到它。很酷的是:在多个城市运行单个 NGINX 可为您提供大量现成的冗余。如果其中一个因某种原因死亡,还有更多的流量要发送到。当您的一台服务器脱机时,其余服务器仍在那里为您的大多数用户提供服务。完成这项工作很乏味但很简单。您有健康检查(另外:当 CDN 区域中断时,它们通常会因缓慢而中断,因此您希望您的健康检查也能检测到)。当您的 NGINX 服务器出现故障时,它们会告诉您。您编写 DNS 更改脚本或撤回 BGP 路由(可能只是通过停止这些区域上的 BGP4 服务)作为响应。

那是服务器故障,很容易发现。互联网打嗝更难检测。您需要从多个位置运行外部运行状况检查。获得基本的、多视角的监控很容易——我们使用 Datadog 和 updown.io,我们正在构建我们自己的半成品服务。您要求的不会比 cURL 告诉您的更多。再说一遍:您在 CDN 中非常担心的是区域变慢,而不是完全脱离 Internet。顺便说一句:请注意,所有这些监控选项都适用于从其他人的数据中心到您的数据中心。 DC-DC 交通是一个好的开始,足以应付很多工作。但不具代表性。您的用户不在数据中心(我希望)。当你真的很受欢迎时,你想要的是从实际客户的有利位置进行监控。为此,您可以找到数百家销售 RUM(真实用户监控)的公司,这通常采用秘密嵌入 Javascript 错误的形式。我们喜欢一种朗姆酒。它由一家名为 Plantation 的公司出售,并在酒桶中陈酿。喝一大口,然后用 Honeycomb 做你自己的仪器。可笑的互联网问题是最糟糕的。但好消息是,每个人都在制定解决方案,因此我们不必过多谈论它们。缓存更有趣。那么让我们来谈谈洋葱。缓存测量中的优值是“缓存比率”。缓存比率衡量我们能够从缓存中提供服务的频率,而不是源。 80% 的缓存率只是意味着“当我们收到一个请求时,我们有 80% 的时间可以从缓存中提供它,剩下的 20% 的时间我们必须将请求代理到源”。如果你正在构建需要 CDN 的东西,高缓存率是好的,低缓存率是坏的。如果您按照文章前面的链接访问 Github 存储库,您可能已经注意到我们的 NGINX 设置是一个独立的单个服务器。在二十个地方部署它给我们二十个单独的服务器。太简单了。但简单是有代价的——没有每个区域的冗余。所有二十台服务器都需要向源发出请求。这很脆弱,缓存比率会受到影响。我们可以做得更好。增加冗余的简单方法是在每个区域添加第二台服务器。但是这样做可能会破坏缓存比率。单个服务器的优点是为所有用户托管一个缓存;有两个,每个源的请求数是两倍,缓存未命中数是两倍。

你想要做的是教你的服务器互相交谈,让他们向他们的朋友询问缓存内容。最简单的方法是创建缓存分片 - 将数据拆分,以便每个服务器负责其中的一个块,其他所有人将请求路由到拥有正确块的缓存分片。这听起来很复杂,但 NGINX 的内置负载均衡器支持基于哈希的负载均衡。它散列请求,并将“相同的请求”转发到相同的服务器,假设该服务器可用。如果您正在玩这篇博文的家庭版,这里有一个现成的 NGINX 集群示例,它可以发现其对等点、散列 URL 并通过可用服务器处理请求。当对 a.jpg 的请求命中我们的 NGINX 实例时,它们都会将请求转发到集群中的同一台服务器。 b.jpg 也一样。此设置将服务器用作负载平衡代理和存储分片。您可以分离这些层,如果您要在 CDN 中构建更高级的功能,您可能希望这样做。我们的集群 NGINX 示例使用了我们认为非常酷的 Fly 功能。持久卷有助于在 NGINX 升级之间保持高缓存率。加密的私有网络使安全的 NGINX 到 NGINX 通信变得简单,并使您不必进行复杂的 mTLS 操练。内置的 DNS 服务发现有助于在我们添加和删除服务器时使集群保持最新状态。如果这听起来有点过于完美匹配,那是因为我们专门为类似 CDN 的工作负载构建了这些功能。但是,当然,您可以在任何地方进行所有这些操作,而不仅仅是在 Fly 上。但在 Fly 上很容易。两个真理:高缓存率是好的,互联网是坏的。如果您喜欢杀鸟和保护石头,您会非常喜欢解决缓存比率和垃圾互联网问题。这两个问题的答案都涉及让互联网的脏手摆脱我们的 HTTP 请求。一种提高缓存率的简单方法:通过您信任的网络绕过失控的 Internet 和代理源请求。 CDN 通常在靠近其客户来源的区域设有服务器。如果您将我们的 NGINX 示例放在弗吉尼亚州,您会突然拥有靠近 AWS 最大区域的服务器。而且您肯定在 AWS 上有客户。这就是与一个巨大的强大垄断者并存的优势!

您可以使用一点 NGINX 和代理魔法,将所有请求通过弗吉尼亚州发送到原始服务器。这很好。弗吉尼亚州的服务器和 us-east-1 中客户的服务器之间的 Internet 熊陷阱较少。现在您拥有一组规范的服务器来处理特定客户的请求。好消息。此设置可提高您的缓存率并避免不良互联网。对于奖励积分,它也是额外 CDN 功能的基础。如果您曾经在 CDN 上购物,就会遇到诸如“屏蔽”和“请求合并”之类的东西。源头屏蔽通常仅意味着通过已知数据中心发送所有流量。这可以最大限度地减少到源服务器的流量,而且,因为您可能知道您的 CDN 区域使用的 IP,您可以使用简单的 L4 防火墙规则来控制访问。合并请求还可以最大限度地减少原始流量,尤其是在许多用户试图获取相同内容的大型活动期间。当 100,000 名用户同时请求您最新的巧妙编写的博客文章,并且尚未缓存时,这可能意味着 100,000 个并发请求到您的源。对于大多数来源来说,这是一个面部融化的流量水平。解决这个问题是“锁定”一个特定的 URL,以确保如果 NGINX 服务器发出原始请求,其他客户端会暂停,直到缓存是文件。在我们的集群 NGINX 示例中,这是一个两行配置。通过单个区域进行代理以提高缓存率有点像作弊。 CDN 的全部目的是为用户加快速度。从新加坡向弗吉尼亚发送请求只会让事情变得更快,因为一组带有缓存内容的 NGINX 服务器几乎总是比原始服务更快。但是,实际上,它很慢而且不受欢迎。澳大利亚的请求可能会在前往弗吉尼亚的途中经过新加坡。甚至光在 14,624 公里(澳大利亚到弗吉尼亚州)上也很慢,因此澳大利亚到新加坡(4,300 公里)的缓存减少了可察觉的延迟。缓存未命中时会慢一点。但我们谈论的是“令人讨厌的慢”和“比令人讨厌的慢还糟糕 150 毫秒”之间的区别。如果您正在构建通用 CDN,这是一个很好的方法。您可以创建一些超级区域,为世界的一部分聚合缓存数据。

如果您不是在构建通用 CDN,而只是想加速您的应用程序,那么这是一个脆弱的解决方案。您最好将应用程序的一部分分发到多个区域。 CDN 的基本思想是陈旧的,并且易于理解。但是,构建 CDN 历来是一项雄心勃勃的团队事业,而不是单个开发人员的周末项目。但是,功能强大的 CDN 的构建块已经在 NGINX 等工具中使用了很长时间。如果你一直在家里玩 Github 存储库,我们希望你已经注意到,即使是我们正在谈论的最复杂的设计迭代,一个具有每个区域冗余的设计,并允许对区域之间的请求路由,大多只是 NGINX 配置 --- 而不是特别复杂的配置。我们添加的“代码”只是足以插入地址的 bash。所以这是一个CDN。它非常适合简单的缓存。对于复杂的应用程序,它只缺少一些东西。值得注意的是,我们根本没有解决缓存过期问题。使用 CDN 的一个铁定规则是:您绝对会在发布版本上输入一个令人尴尬的拼写错误,发现为时已晚,并且发现您所有的缓存服务器都有一个名为“A Better Amercia”的副本。分布式缓存失效对于 CDN 来说是一个大而棘手的问题。有人可以写一整篇关于它的文章。 CDN 层也是添加应用程序功能的绝佳场所。图像优化、WAF、API 速率限制、机器人检测,我们可以继续。有人可以把这些变成十多篇文章。最后一件事。就像我们之前提到的:整篇文章都是偏见。我们强调这个 CDN 设计是因为我们构建了一个平台,它可以非常容易地表达(你应该玩它)。那些使构建 CDN on Fly 变得微不足道的相同平台功能也使分发整个应用程序变得容易;为边缘分发设计的应用程序可能根本不需要 CDN。

在 Twitter 上分享这篇文章 在 Hacker News 上分享这篇文章 在 Reddit 上分享这篇文章