Cloudflare Workers 性能:与 Astro 和全球延迟的实验
为何使用 Cloudflare Workers?
Cloudflare Workers 允许托管页面和运行代码,而无需管理服务器。与传统服务器放置在单个或少数几个位置不同,部署的静态资产和代码在全球数据中心(如下方蓝点所示)镜像。这自然提供了更好的延迟、可扩展性和鲁棒性。
其开发者平台还超越了“Workers”(计算部分),包括存储、数据库、队列、AI 和许多其他开发者工具。所有这些都提供一个慷慨的免费层和超出部分的合理定价。
撰写本文的原因:发现它相当不错,有良好的使用体验,因此在此介绍。本文并非赞助。开发者有责任交流他们使用的工具,以保持其生态系统活跃。曾见过太多好东西因为缺乏“热度”而被抛弃。
使用 Cloudflare Workers 的好处是:
- 全球范围内出色的延迟
- 无限的可扩展性
- 无需维护服务器
- 数据、文件、AI 等的进一步工具
- GitHub 拉取请求预览 URL
- 免费层对大多数业余项目足够好
何时不使用它
与所有工具一样,它有它擅长的用例,也有不适合的用例。理解底层技术非常重要。基本上,它会将整个应用打包为脚本并在运行时动态评估。如果您的 API 和使用的框架是轻量级和极简主义的,它会很快并且效果很好。然而,在以下用例中不建议使用:
- 大型复杂应用:评估您的 API/SSR 脚本的成本会随着应用的增大而增加。它变得越大,整个调用就越低效。还有一些限制是关于“脚本”可以有多大。尽管过去已经多次提高此限制,但它效率极低的事实将始终存在。因此,在选择依赖项/框架时要小心,因为它们可能迅速使您的代码库膨胀。
- 资源消耗大:由于其性质,它不适合需要大量 CPU/内存/时间的计算,如统计模型或科学计算。大型缓存也有问题。等待长时间运行的异步服务器端请求是可以的,执行在中间暂停,不计入执行时间。
- 长连接:这也是有问题的。应该使用轮询而不是保持连接开放。
换句话说:“越轻量越好!”
很难说什么足够小,什么时候变得太大。这更适合于适度大小的小型自包含微服务。即使使用断点调试也可能变得具有挑战性。对于此类大型应用程序,传统的服务器部署会更合适。
我们将构建什么?
一个“每日名言”Web 应用程序。
目的不是构建一个大型的东西,而是一个简单的概念验证。名言将存储在 KV 存储中并在客户端获取。这样,我们可以测量整个工作的速度以及它是否符合期望。
https://quoted.day 的默认版本有两种风格:
- https://quoted.day/spa:一个静态页面,异步获取名言文本/作者
- https://quoted.day/ssr:服务器端渲染,在服务器上渲染带有名言的页面
为了进行实验,会不时交换哪个是默认版本。性能(延迟)可能因您所在的位置以及您获取的内容是“热”还是“冷”而异。在深入研究如何构建此类应用之前,让我们先看看可以预期的性能。
全球延迟基准测试
与在某中心内部测量的、因此相当乐观的内部 Cloudflare 延迟测量不同,我们将使用出色的工具 https://www.openstatus.dev/play/checker 来查看“真实的”外部延迟。
借助此工具,我们可以很好地了解全球各地可观察到的总体延迟。但请注意,澳大利亚、亚洲和非洲的延迟可能有时相当不稳定,会出现“跳跃”。
我们还将分别对多个事物进行基准测试:
- 静态资产
- 无状态函数
- 热 KV 读取
- 冷 KV 读取
- KV 写入
此外,每种情况都会进行“两次传递”,以希望在此过程中填充缓存,并仅记录第二次结果。
静态资产
通过获取主页面 https://quoted.day/spa 获得。
| 区域 | 延迟 |
|---|---|
| 🇩🇪 德国法兰克福 | 30ms |
| 🇩🇪 德国法兰克福 (koyeb) | 31ms |
| 🇫🇷 法国巴黎 | 33ms |
| 🇳🇱 荷兰阿姆斯特丹 (railway) | 33ms |
| 🇬🇧 英国伦敦 | 31ms |
| 🇸🇪 瑞典斯德哥尔摩 | 32ms |
| 🇫🇷 法国巴黎 (koyeb) | 31ms |
| 🇳🇱 荷兰阿姆斯特丹 | 54ms |
| 🇺🇸 美国新泽西州西考克斯 | 32ms |
| 🇺🇸 美国弗吉尼亚州阿什本 | 36ms |
| 🇺🇸 美国华盛顿 (koyeb) | 35ms |
| 🇨🇦 加拿大多伦多 | 50ms |
| 🇺🇸 美国伊利诺伊州芝加哥 | 36ms |
| 🇺🇸 美国加利福尼亚州洛杉矶 | 28ms |
| 🇺🇸 美国加利福尼亚州圣何塞 | 26ms |
| 🇺🇸 美国弗吉尼亚州 (railway) | 41ms |
| 🇺🇸 美国加利福尼亚州 (railway) | 49ms |
| 🇺🇸 美国旧金山 (koyeb) | 29ms |
| 🇸🇬 新加坡 (railway) | 53ms |
| 🇮🇳 印度孟买 | 95ms |
| 🇺🇸 美国德克萨斯州达拉斯 | 30ms |
| 🇯🇵 日本东京 | 28ms |
| 🇦🇺 澳大利亚悉尼 | 31ms |
| 🇸🇬 新加坡 | 294ms |
| 🇸🇬 新加坡 (koyeb) | 436ms |
| 🇧🇷 巴西圣保罗 | 252ms |
| 🇿🇦 南非约翰内斯堡 | 559ms |
| 🇯🇵 日本东京 (koyeb) | 28ms |
无状态函数
通过获取端点 https://quoted.day/api/time 获得,该端点仅返回当前时间。
| 区域 | 延迟 |
|---|---|
| 🇬🇧 英国伦敦 | 38ms |
| 🇩🇪 德国法兰克福 (koyeb) | 32ms |
| 🇳🇱 荷兰阿姆斯特丹 (railway) | 36ms |
| 🇫🇷 法国巴黎 | 75ms |
| 🇳🇱 荷兰阿姆斯特丹 | 76ms |
| 🇩🇪 德国法兰克福 | 88ms |
| 🇫🇷 法国巴黎 (koyeb) | 73ms |
| 🇸🇪 瑞典斯德哥尔摩 | 97ms |
| 🇺🇸 美国弗吉尼亚州 (railway) | 36ms |
| 🇺🇸 美国华盛顿 (koyeb) | 62ms |
| 🇺🇸 美国新泽西州西考克斯 | 95ms |
| 🇺🇸 美国加利福尼亚州洛杉矶 | 39ms |
| 🇺🇸 美国加利福尼亚州圣何塞 | 25ms |
| 🇺🇸 美国弗吉尼亚州阿什本 | 92ms |
| 🇺🇸 美国德克萨斯州达拉斯 | 90ms |
| 🇨🇦 加拿大多伦多 | 22ms |
| 🇺🇸 美国伊利诺伊州芝加哥 | 108ms |
| 🇮🇳 印度孟买 | 99ms |
| 🇸🇬 新加坡 (railway) | 45ms |
| 🇯🇵 日本东京 | 27ms |
| 🇺🇸 美国加利福尼亚州 (railway) | 99ms |
| 🇧🇷 巴西圣保罗 | 89ms |
| 🇦🇺 澳大利亚悉尼 | 26ms |
| 🇸🇬 新加坡 | 220ms |
| 🇺🇸 美国旧金山 (koyeb) | 26ms |
| 🇿🇦 南非约翰内斯堡 | 540ms |
| 🇸🇬 新加坡 (koyeb) | 354ms |
| 🇯🇵 日本东京 (koyeb) | 71ms |
热 KV 读取
通过使用端点 https://quoted.day/api/quote/123 从 KV 存储中获取固定名言获得。
| 区域 | 延迟 |
|---|---|
| 🇬🇧 英国伦敦 | 34ms |
| 🇫🇷 法国巴黎 | 39ms |
| 🇳🇱 荷兰阿姆斯特丹 (railway) | 35ms |
| 🇫🇷 法国巴黎 (koyeb) | 37ms |
| 🇸🇪 瑞典斯德哥尔摩 | 34ms |
| 🇳🇱 荷兰阿姆斯特丹 | 77ms |
| 🇩🇪 德国法兰克福 (koyeb) | 103ms |
| 🇨🇦 加拿大多伦多 | 25ms |
| 🇺🇸 美国德克萨斯州达拉斯 | 33ms |
| 🇺🇸 美国华盛顿 (koyeb) | 55ms |
| 🇩🇪 德国法兰克福 | 168ms |
| 🇺🇸 美国弗吉尼亚州阿什本 | 106ms |
| 🇺🇸 美国加利福尼亚州 (railway) | 52ms |
| 🇺🇸 美国新泽西州西考克斯 | 122ms |
| 🇺🇸 美国旧金山 (koyeb) | 33ms |
| 🇺🇸 美国弗吉尼亚州 (railway) | 123ms |
| 🇿🇦 南非约翰内斯堡 | 43ms |
| 🇮🇳 印度孟买 | 99ms |
| 🇸🇬 新加坡 (railway) | 88ms |
| 🇺🇸 美国伊利诺伊州芝加哥 | 69ms |
| 🇧🇷 巴西圣保罗 | 99ms |
| 🇺🇸 美国加利福尼亚州圣何塞 | 40ms |
| 🇦🇺 澳大利亚悉尼 | 64ms |
| 🇺🇸 美国加利福尼亚州洛杉矶 | 91ms |
| 🇸🇬 新加坡 | 345ms |
| 🇯🇵 日本东京 | 126ms |
| 🇯🇵 日本东京 (koyeb) | 65ms |
| 🇸🇬 新加坡 (koyeb) | 856ms |
冷 KV 读取
通过使用端点 https://quoted.day/api/quote 从 KV 存储中获取随机名言获得。
请注意,每次调用将在边缘位置缓存结果一天,随着流量的增加,可能会将冷读取变为热读取。
| 区域 | 延迟 |
|---|---|
| 🇩🇪 德国法兰克福 | 131ms |
| 🇩🇪 德国法兰克福 (koyeb) | 105ms |
| 🇬🇧 英国伦敦 | 110ms |
| 🇳🇱 荷兰阿姆斯特丹 | 130ms |
| 🇫🇷 法国巴黎 | 145ms |
| 🇸🇪 瑞典斯德哥尔摩 | 134ms |
| 🇫🇷 法国巴黎 (koyeb) | 127ms |
| 🇳🇱 荷兰阿姆斯特丹 (railway) | 133ms |
| 🇺🇸 美国新泽西州西考克斯 | 197ms |
| 🇺🇸 美国伊利诺伊州芝加哥 | 201ms |
| 🇺🇸 美国弗吉尼亚州阿什本 | 220ms |
| 🇨🇦 加拿大多伦多 | 243ms |
| 🇺🇸 美国华盛顿 (koyeb) | 229ms |
| 🇺🇸 美国德克萨斯州达拉斯 | 287ms |
| 🇺🇸 美国弗吉尼亚州 (railway) | 270ms |
| 🇸🇬 新加坡 | 288ms |
| 🇺🇸 美国加利福尼亚州圣何塞 | 245ms |
| 🇮🇳 印度孟买 | 502ms |
| 🇿🇦 南非约翰内斯堡 | 322ms |
| 🇸🇬 新加坡 (railway) | 323ms |
| 🇺🇸 美国加利福尼亚州洛杉矶 | 247ms |
| 🇺🇸 美国旧金山 (koyeb) | 217ms |
| 🇺🇸 美国加利福尼亚州 (railway) | 300ms |
| 🇧🇷 巴西圣保罗 | 601ms |
| 🇯🇵 日本东京 | 822ms |
| 🇸🇬 新加坡 (koyeb) | 574ms |
| 🇯🇵 日本东京 (koyeb) | 335ms |
| 🇦🇺 澳大利亚悉尼 | 964ms |
KV 写入
通过调用 quoted.day/api/bump-counter 获得,它创建一个具有 10 分钟过期时间的临时 KV 对。这有点模拟了启动“会话”的概念。
| 区域 | 延迟 |
|---|---|
| 🇫🇷 法国巴黎 | 128ms |
| 🇩🇪 德国法兰克福 (koyeb) | 151ms |
| 🇩🇪 德国法兰克福 | 147ms |
| 🇫🇷 法国巴黎 (koyeb) | 194ms |
| 🇳🇱 荷兰阿姆斯特丹 | 145ms |
| 🇸🇪 瑞典斯德哥尔摩 | 240ms |
| 🇬🇧 英国伦敦 | 176ms |
| 🇺🇸 美国德克萨斯州达拉斯 | 212ms |
| 🇺🇸 美国加利福尼亚州 (railway) | 238ms |
| 🇺🇸 美国华盛顿 (koyeb) | 305ms |
| 🇺🇸 美国弗吉尼亚州 (railway) | 295ms |
| 🇺🇸 美国新泽西州西考克斯 | 408ms |
| 🇺🇸 美国弗吉尼亚州阿什本 | 423ms |
| 🇨🇦 加拿大多伦多 | 337ms |
| 🇺🇸 美国伊利诺伊州芝加哥 | 359ms |
| 🇸🇬 新加坡 (koyeb) | 409ms |
| 🇺🇸 美国加利福尼亚州洛杉矶 | 335ms |
| 🇮🇳 印度孟买 | 347ms |
| 🇺🇸 美国加利福尼亚州圣何塞 | 438ms |
| 🇺🇸 美国旧金山 (koyeb) | 247ms |
| 🇸🇬 新加坡 | 508ms |
| 🇯🇵 日本东京 | 684ms |
| 🇦🇺 澳大利亚悉尼 | 713ms |
| 🇯🇵 日本东京 (koyeb) | 734ms |
| 🇳🇱 荷兰阿姆斯特丹 (railway) | 1,259ms |
| 🇸🇬 新加坡 (railway) | 1,139ms |
| 🇿🇦 南非约翰内斯堡 | 2,266ms |
具有 KV 冷读取的 SSR 页面
最后,在这个测试中,结合读取随机名言(通常会导致冷 KV 读取)并在页面中服务器端渲染它。
| 区域 | 延迟 |
|---|---|
| 🇫🇷 法国巴黎 (koyeb) | 111ms |
| 🇬🇧 英国伦敦 | 108ms |
| 🇳🇱 荷兰阿姆斯特丹 (railway) | 125ms |
| 🇫🇷 法国巴黎 | 133ms |
| 🇩🇪 德国法兰克福 (koyeb) | 139ms |
| 🇩🇪 德国法兰克福 | 146ms |
| 🇸🇪 瑞典斯德哥尔摩 | 142ms |
| 🇳🇱 荷兰阿姆斯特丹 | 70ms |
| 🇺🇸 美国弗吉尼亚州 (railway) | 151ms |
| 🇺🇸 美国华盛顿 (koyeb) | 159ms |
| 🇺🇸 美国新泽西州西考克斯 | 201ms |
| 🇺🇸 美国弗吉尼亚州阿什本 | 209ms |
| 🇺🇸 美国伊利诺伊州芝加哥 | 217ms |
| 🇺🇸 美国德克萨斯州达拉斯 | 220ms |
| 🇺🇸 美国加利福尼亚州圣何塞 | 191ms |
| 🇺🇸 美国加利福尼亚州 (railway) | 201ms |
| 🇨🇦 加拿大多伦多 | 255ms |
| 🇺🇸 美国加利福尼亚州洛杉矶 | 257ms |
| 🇺🇸 美国旧金山 (koyeb) | 268ms |
| 🇮🇳 印度孟买 | 422ms |
| 🇯🇵 日本东京 | 332ms |
| 🇸🇬 新加坡 | 284ms |
| 🇧🇷 巴西圣保罗 | 327ms |
| 🇸🇬 新加坡 (railway) | 632ms |
| 🇸🇬 新加坡 (koyeb) | 677ms |
| 🇿🇦 南非约翰内斯堡 | 673ms |
| 🇦🇺 澳大利亚悉尼 | 385ms |
| 🇯🇵 日本东京 (koyeb) | 350ms |
观察结果
通过观察数字来推断 KV 的工作方式很有趣。似乎 KV 存储不是主动复制的,而是 KV 对在远程位置“按需”复制。当被缓存时(默认 1 分钟),后续读取很快。此类“热”KV 对的延迟总体上相当不错。这里没有问题。该对在缓存中保留多长时间也可以在 KV get 请求期间使用cacheTtl参数进行配置。然而,增加该值的缺点是,在此期间,此缓存副本不反映其他位置触发的变化/更新。
不出所料,冷读取的延迟更差。从数字中可以推断出的另一件事是,似乎存在一个“源位置”,冷读取的延迟根据到该位置的距离成比例地增加。因此,请注意“在哪里”创建 KV 存储,因为它会影响全球所有未来的延迟。请注意,Workers KV 将来可能会改变,这仅仅是其当前状态的观察。
虽然读操作还可以,但写操作目前相当令人失望。原本期望它也有很好的延迟,写入“边缘”并让传播异步进行,但情况恰恰相反。写入似乎与“源”存储通信。设置一个值所需的时间取决于您距离创建 KV 存储的位置有多远。这有点糟糕,因为设置/更新值是一个非常常见的操作,例如对用户进行身份验证。亲爱的某中心团队,希望将来改进这一部分。
注意事项
如果您开发 Web 应用、发布它并查看它,您可能甚至不会注意到糟糕的延迟。您将面临最佳的延迟,源 KV 存储就在您附近。然而,在地球另一端的人将会有更差的体验。如果那个人有一些缓存未命中或写入,响应时间可能会迅速攀升到几秒钟,然后响应才到达。这不是期望的“分布式”KV 存储的行为方式。让我们明确一点,目前它的行为更像是具有边缘按需缓存副本的集中式 KV 存储。
相当讽刺的是,目前它基本上感觉更像传统的单位置数据库(+缓存)。虽然单个缓存未命中或单个写入的延迟并不严重,但随着多次调用,它可能会迅速累积,尤其是写入量大的 Web 应用,可能会根据其位置面临增加的“迟缓”现象。同样,在使用 Workers 构思 Web 应用时,应牢记在 KV 调用方面保持“极简主义”。
最后,Worker 中还有一个可用的设置:“默认放置”与“智能放置”。两者都尝试过,但在延迟方面没有看到明显变化。我认为这是由于只有单个 KV 存储调用,并且需要时间和流量来收集遥测数据并调整 Workers 的放置。这可能很棒,但对于这个实验,它根本没有效果。
单页应用程序与服务器端渲染
同样,没有一个普遍比另一个更好或更差,答案取决于具体情况。
除了框架和整体架构的强烈差异外,对于最终用户来说,它也有实际的根本差异。看到历史重演也很有趣,互联网最初从服务器渲染页面开始,然后单页应用程序数据获取占据了主导地位,而 SSR 再次兴起,就像过去一样,只是有了新的技术栈。
SSR 实际上是最容易解释的:在服务器端获取所有所需数据,将所有内容放入模板,并将结果页面返回给最终用户。它在服务器端花费一些时间和处理能力,不可缓存,但客户端获得一个“已完成”的页面。
SPA 则相反。虽然 HTML/CSS/JS 是静态的并且被缓存(因此获取很快),但由于需要所有客户端 JavaScript 库,资源通常要大得多。然后开始繁重的工作,获取数据并渲染页面,通常显示一个加载指示器。因此,渲染页面的总时间更长。
然而,之后与 SPA 的交互通常更流畅,因为交互只是与服务器交换数据并对页面进行本地更改。相比之下,SSR 意味着导航和加载新页面。因此,选择 SPA 还是 SSR 更适合取决于页面/应用应该有多“交互”。
作为经验法则,如果它更像静态“网页”,选择 SSR;如果它更像交互式“Web 应用”,选择 SPA。
最后,选择 Astro 作为示例的好处在于,整个频谱都是可能的:静态页面、SPA 和 SSR。
来源
此实验的源代码位于:https://github.com/dagnelies/quoted-day
如果您有 Github 和 Cloudflare 帐户,也可以通过单击此处进行 fork 和部署:
https://deploy.workers.cloudflare.com/?url=https://github.com/dagnelies/quoted-day
它将 fork GitHub 存储库并将其部署在内部 URL 上,以便您可以预览。之后,您可以编辑代码,它将自动部署,等等。
请注意,示例引用了一个 KV 存储,该存储是我的。因此,您必须创建自己的名为 QUOTES 的 KV 存储,并用您的 KV id 替换wrangler.json文件中的 QUOTES KV id。如果希望重现该示例,还必须最初用名言填充它。幸运的是,package.json中有脚本来做到这一点。
此后的所有内容都值得单独写一个教程。这仅仅是一个实验的结果,延迟如何维持,以及对该平台的一些见解。享受吧!FINISHED
更多精彩内容 请关注我的个人公众号 公众号(办公AI智能小助手)或者 我的个人博客 https://blog.qife122.com/
对网络安全、黑客技术感兴趣的朋友可以关注我的安全公众号(网络安全技术点滴分享)