使用 Cloudflare Workers 在边缘启用服务从附近位置获取数据 - 降低全球平均延迟。

来自webp cloud: Using Cloudflare Workers at the edge to enable services to source data from nearby locations – reducing global average latency. - WebP Cloud Services Blog

如果你有一个服务并希望在全球范围内实现快速响应时间,你的初步想法是什么?CDN?GeoDNS?

让我们从一个简单的例子开始。假设你有一个服务,其目的是为访问者提供一个 UUID。想象你的服务部署在德国,你的服务器 IP 地址是 159.69.27.1 。然后,你配置 DNS 将 uuid.example.com 指向这个 IP 地址。因此,当有人访问 uuid.example.com 时,他们会收到一个 UUID。

这是第一步:当任何人访问 uuid.example.com 时,它会解析到相应的 IP 地址。然后,浏览器发起请求,经过几秒钟后,成功返回所需的结果。然而,来自世界不同地区的用户可能会经历不同的延迟。
例如,从中国大陆的延迟可能会是这样的:

PING 159.69.27.1 (159.69.27.1) 56(84) bytes of data.
64 bytes from 159.69.27.1: icmp_seq=1 ttl=37 time=373 ms
64 bytes from 159.69.27.1: icmp_seq=2 ttl=37 time=423 ms
64 bytes from 159.69.27.1: icmp_seq=6 ttl=37 time=388 ms

而从美国的延迟情况如下:

PING 159.69.27.1 (159.69.27.1) 56(84) bytes of data.
64 bytes from 159.69.27.1: icmp_seq=1 ttl=56 time=114 ms
64 bytes from 159.69.27.1: icmp_seq=2 ttl=56 time=113 ms
64 bytes from 159.69.27.1: icmp_seq=4 ttl=56 time=113 ms

为了防止暴露你的服务器 IP 地址,这可能会导致攻击,并且为了“加速访问”,许多人可能会选择使用 CDN,如 Cloudflare。当你将你的域名与 Cloudflare 集成时,你会注意到 uuid.example.com 不再解析到你的服务器 IP,而是可能会指向由 Cloudflare 拥有的 IP,如 104.16.132.229

在这个阶段,当其他请求访问你的服务时,你会观察到使用 ping 命令时的延迟大约为 10 毫秒:

PING 104.16.133.229 (104.16.133.229) 56(84) bytes of data.
64 bytes from 104.16.133.229: icmp_seq=1 ttl=62 time=10.8 ms
64 bytes from 104.16.133.229: icmp_seq=2 ttl=62 time=10.2 ms
64 bytes from 104.16.133.229: icmp_seq=3 ttl=62 time=10.3 ms

在这个阶段,根据 ping 的结果,延迟似乎已经降低了,这让许多人认为实际服务的延迟已经改善。然而,重要的是要注意,这种延迟只是从你的访问者到 Cloudflare 的 Anycast 节点。虽然 ping 时间很低,但实际的 HTTP 请求仍然需要通过公共互联网到达你的源服务器。要测量实际的延迟,你需要查看首字节时间(TTFB)。

KeyCDN 提供了一个 性能测试 ,可以轻松评估服务在各个主要地区的延迟。以 WebP Cloud Services—公共服务为例,由于您的服务器全部位于德国的 Hetzner,因此得益于 Cloudflare 的 CDN,CONNECT 延迟较低。
然而,在检查 TTFB 时,您会注意到只有德国地区的延迟较低,而其他地区的延迟超过 100 毫秒。

WebP Cloud Services—公共服务为 Gravatar 和 GitHub Avatars 提供反向代理,解决了两个主要问题:

  1. 中国大陆用户无法直接访问 Gravatar,例如这个地址: https://www.gravatar.com/avatar/09eba3a443a7ea91cf818f6b27607d66
  2. 在提供这些图像时,它提供了 WebP 转换,显著减小了图像文件大小,对图像质量的影响 minimal,从而加速了整个网站的加载速度。

此外,该服务是公开的且完全免费的,拥有大量用户,包括但不限于像 CNX SoftwareIndienova 这样的网站。

从 Cloudflare 的统计仪表板中,我们可以看到在过去 30 天内,该服务处理了超过 600 万次请求,其中大部分来自美国和中国:

值得注意的是,除了中国移动用户外,大多数中国访问者被路由到 Cloudflare 位于西部的美国节点,通常位于 SJC。

根据我们的理论,大约有一半的用户首先访问 Cloudflare 的美国西部节点,然后通过公共互联网到达我们在德国的源服务器,导致额外的延迟超过 110 毫秒。这给用户留下了服务响应时间缓慢的印象。

那么,我们应该如何解决这个问题?

在这种情况下,存在几个隐含条件:

  • 我们需要继续使用 Cloudflare 来保护我们的源服务器地址,并在 Cloudflare 的边缘执行某些计算和 WAF 规则。
  • 我们不能直接将服务“迁移”到美国,因为我们仍然有欧洲用户。因此,我们需要在美国和欧洲都设有服务器。
  • 鉴于大多数访问者来自美国和中国,且中国用户被路由到美国节点,我们的优化重点应放在提高美国的访问速度上。
  • 我们的目标是将美国和中国的用户引导至美国的服务器,将欧洲用户引导至德国的服务器,并将其他地区的用户引导至最近的服务器。

考虑到这些因素,我们提出了几种解决方案:

  1. 使用私有 ASN + IPv6: 在美国和欧洲使用像 Vultr 这样的服务部署节点,并使用 BGP Anycast 进行负载均衡。这种方法与 Nova 的博客文章中讨论的类似,文章标题为“ Simulate Argo——在 Cloudflare 背后构建 IPv6 AnyCast 网络 。”成本包括 ASN 费用、IPv6 成本、Vultr 费用以及维护网络的开销,使其有些复杂。
  2. 直接使用 BuyVM 的 Anycast 服务: 在三个地点(美国和欧洲)购买 VPS,并使用 BuyVM 的 Anycast 服务进行负载均衡。此选项涉及 BuyVM 的 VPS 费用,大约每月 10.5 美元。
  3. 使用 Cloudflare 负载均衡器进行地理负载均衡: 利用 Cloudflare 负载均衡器进行地理负载均衡。费用包括 Cloudflare 负载均衡器费用,大约每月 5 美元可处理 50 万个请求,超出 50 万个请求的每 50 万个请求额外收取 0.5 美元。
    如果我们旨在根据我们的需求实现基于区域的路由,成本将是每月20.5美元,基于我们每月600万的请求。
  4. 使用 Cloudflare Workers: 利用 Cloudflare Workers,这是一项在 Cloudflare 所有数据中心部署的无服务器服务。费用包括 Cloudflare Workers 的费用,大约每月 5 美元(处理高达每月 1000 万次请求,超出我们的月请求量)。

从上述计划来看,使用 Cloudflare Workers 似乎是更无忧的解决方案。付费并让我们开始吧!

Cloudflare Workers

Cloudflare Workers 是 Cloudflare 提供的一种无服务器服务,允许我们在 Cloudflare 的所有数据中心部署代码。它还使我们能够使用 Cloudflare Workers KV 存储数据,让我们能够在全球范围内部署代码并在全球范围内读写数据。

对于我们的特定用例,使用 Cloudflare Workers 的主要逻辑如下:

  • 在 Workers 平台上,根据源 IP 地址(该地址极有可能为执行 Workers 机器的位置)确定请求的来源国家。
  • 根据预定义的映射,将请求路由到物理上最接近的服务器。
  • 此外,处理各种类型的异常请求并实现自动故障转移逻辑。

https://developers.cloudflare.com/fundamentals/reference/http-request-headers/#cf-ipcountry ,我们可以了解到,对于每个请求,我们可以使用 CF-IPCountry 获取请求来源区域的代码。在我们的案例中,为了简化,我们计划根据大陆来分配流量。因此,我们可以快速创建如下简单的映射:

function getContinentByISOCode(isoCode) {
    const continentMap = {
        'AD': 'Europe',
        'AE': 'Asia',
        ...
        'CN': 'North America', // China should be Asia, but we're using North America because China users are routed to the North America Edge
        ...
        'ZW': 'Africa',
      };
  
    const continent = continentMap[isoCode];
  
    if (continent) {
      return continent;
    } else {
      return 'Unknown';
    }
  }

下一步是在各个地区启动服务,规划服务的端点地址,并根据以下大陆在地理位置上映射我们的实际后端服务:

const BACKEND_MAP = {
    ...
    'Europe': 'https://eu-west-2-entrance.webp.se',
    'North America': 'https://us-west-2-entrance.webp.se',
    ...
    'Unknown': 'https://eu-west-1-entrance.webp.se'
}

最后,我们的 Workers 代码可以看起来像这样:

handleProxy 函数如下:

这不是很简单吗?

请注意, handleProxy(request, backend_url, path, url.hostname, CF_IP_COUNTRY); 函数有三个参数: backend_urlpathurl.hostname 。这是因为我们的服务是通过像 gravatar.webp.se 这样的地址从外部访问的,而不是通过 eu-west-2-entrance.webp.se 。然而,当 Workers 使用 fetch() 访问源时,它只能使用后者。因此,在这里我们需要在 fetch() 中传递一个额外的头部信息,以告知后端服务实际请求的域名。

例如,在一个 Fetch 请求中, Host 头是 eu-west-2-entrance.webp.se ,而 some-secret-header-to-backend 头是 gravatar.webp.se 。当我们的实际后端检测到 some-secret-header-to-backend 头的存在时,它会将此头视为评估的 Host。

效果对比

在使用 Workers 之前,所有源服务器都在德国的 Hetzner。

在使用 Workers 后,源服务器位于 Hetzner 德国和 Hetzner Hillsboro。您可以看到在美国的两个测试点,TTFB 从 300+毫秒显著下降到 100+毫秒。

您可以通过 x-powered-by 头部确定哪个区域的节点正在处理请求:

Hetzner Germany

Hetzner Hillsboro

截至本文发布时,我们已经在这个架构上运行了将近 3 天。根据监控数据,HIO 节点在启动后立即接管了大约一半的流量,这与我们的预期相符。

Workers 的统计数据:

参考文献

  1. https://developers.cloudflare.com/fundamentals/reference/http-request-headers/#cf-ipcountry
  2. 模拟 Argo——在 Cloudflare 背后构建 IPv6 AnyCast 网络