跳到正文

我们如何将云浏览器的成本降低至三分之一并提升速度

我们将浏览器会话成本从每小时 $0.06 降至 $0.02,同时让浏览器启动和扩展速度更快。

本文目录文章开头
部分图表可能显示不完整

译文尚不完整,未译段落保留原文;可打开原文对照。

Browser Use: how we made cloud browsers 3x cheaper — every browser is its own Firecracker microVM on regular EC2, at $0.02 per browser hour with sub-400ms VM cold start and headless stealth.:图片未能保存,请查看原文。

我们的云浏览器需要同时做到三件事:快速启动、保持隔离、成本低廉。这就是我们重建 Browser Use Cloud 的原因,使得新会话能在一秒内启动,每个浏览器小时的成本降至 $0.02,此前为 $0.06。

这比听起来要难。浏览器包含 Chromium、文件系统、cookie、缓存、代理设置、下载内容,有时还有已登录的客户会话。如果一个浏览器能读取另一个浏览器的状态,就会产生安全问题。

常规答案是虚拟机,即 VM。VM 是计算机中的计算机:它拥有自己的 CPU、内存、磁盘和网络设备。它与宿主机上的其他一切分离,如果浏览器崩溃、泄露信息或遭到攻击,损害会停留在 VM 内部。

然而,普通 VM 对云浏览器来说太重了。我们需要不断创建它们,有时一次创建数千个,并在会话结束时立即销毁。如果每个浏览器都需要一个缓慢、昂贵的 VM,产品也会变得缓慢且昂贵。

对我们来说,问题在于我们能否让每个浏览器都拥有自己的 VM,同时不让用户等待或为此付费。我们现在通过 Firecracker 实现了这一点,这是一个轻量级 VM 系统。

每个 Browser Use Cloud 会话都在自己独立的微型 VM 中运行。这些 VM 运行在 EC2 上,即亚马逊的租用云服务器。

这就是不寻常的地方。Firecracker 通常运行在裸金属服务器上,即你租用整台物理机器。为了降低客户成本,我们将其运行在常规 EC2 上,AWS 已经将你的服务器置于一个 VM 之中。

这本应很慢。嵌套 VM 会使内存和 CPU 操作开销增大,而 Chromium 启动也需要时间。本文将介绍我们如何让这套配置变得快速且高效。

但首先,我们为什么要重建基础设施?

A three-circle Venn diagram with fast to start, isolated, and cheap, overlapping in the middle where all three are achieved at once.:图片未能保存,请查看原文。
要同时做到快速、隔离和廉价是很难的。

我们为何接管基础设施层文章图表

当我们最初基于 Unikraft 构建浏览器基础设施时,它开箱即用地提供了我们需要的许多功能:快速启动时间、浏览器快照、缩容至零以及强大的隔离性。

随着工作负载的增长,我们遇到了另一个挑战:扩展底层 EC2 集群本身。

虽然 Unikraft 能够很好地处理浏览器生命周期管理,但当时我们的配置无法使用原生的 EC2 水平自动扩缩容。我们知道这一功能在 Unikraft 的路线图上,但我们急需它,因此我们决定接管该技术栈的这一部分,直接基于 Firecracker 进行构建。

需要指出的是,这并非关于浏览器的扩缩容。Unikraft 已经有效地处理了浏览器的待机、唤醒和缩容至零。我们遇到的限制是在基础设施层,而非浏览器层。配置新节点是一个手动过程,当请求容量超过机器容量时,可能会导致停机。

换句话说,我们离开并不是因为他们无法运行高效的浏览器。我们接管基础设施层是因为我们需要平台中尚未提供的功能。我们仍然期待未来能继续与 Unikraft 合作 :)

Two line charts comparing capacity against demand. Before the rebuild, EC2 fleet capacity stays flat during a traffic spike and lags behind; after the rebuild, capacity tracks demand automatically.:图片未能保存,请查看原文。
在重建之前,EC2 集群容量必须手动添加,因此可能会滞后于流量高峰。重建之后,容量会自动跟踪需求。

Firecracker 提供了一个层,你可以通过它创建、监控和运行 VM。它为每个 VM 提供 CPU、内存、磁盘和网络设备,并使其与宿主机及其他 VM 保持隔离。

教会浏览器自行扩展文章图表

Firecracker 为每个浏览器提供了自己的虚拟机。但它并没有从根本上解决导致旧系统崩溃的问题:运行多少个虚拟机、将它们放在哪里,以及何时增加更多虚拟机。

因此我们构建了自己的控制平面。控制平面监控我们的浏览器集群,并决定我们是否应该扩容或缩容。

当用户请求浏览器时,控制平面会选择一台有空闲容量的机器。当流量上升时,它会启动更多机器。当流量下降时,它会停止向我们想要移除的机器发送新的浏览器。

它实时检查集群。这比等待 AWS 的监控服务 CloudWatch 要快得多,后者通常以一分钟为窗口做出反应。它还知道通用指标所不知道的事情:仍在启动的浏览器、我们正试图移除的机器,以及不应接收新会话的机器。

User code connects through edge routers to the control plane, which sends the session to an EC2 host with room while keeping a draining host out:图片未能保存,请查看原文。
请求从用户代码流经无状态边缘路由器;控制平面选择一台有空闲容量的 EC2 主机,并持续将主机移出服务。

为什么我们在虚拟机内运行虚拟机文章图表

一旦我们有了控制平面,下一个问题是它应该添加什么样的机器。

在 AWS 上运行 Firecracker 的通常方式是使用 .metal 实例。这意味着你租用了整台物理服务器,Firecracker 直接在其上运行。

我们选择了普通 EC2。普通 EC2 机器获取更快,保留成本也更低。我们的主机从预构建的镜像启动,启动后约 30 秒即可开始提供浏览器服务。我们能越快添加主机,需要为闲置容量支付的费用就越少,传递给客户的成本也越低。

问题在于,普通 EC2 本身就是一个虚拟机。AWS 将我们的主机运行在其自身的隔离层内,然后我们在该主机内运行浏览器虚拟机。换句话说,每个浏览器都是虚拟机中的虚拟机。

这不是使用 Firecracker 的常规方式。当浏览器虚拟机需要主机的帮助时,请求要经过两层虚拟机而不是一层,从而增加了延迟。

我们决定这种权衡是值得的,因为普通 EC2 能给我们更快的扩容速度和更低的成本。为了减轻嵌套虚拟化的影响,我们专注于让 Firecracker 尽可能快速。

On a .metal host a browser VM runs on Firecracker on a physical server; on regular EC2, an extra AWS hypervisor layer sits underneath, so a page fault can cross both VM layers:图片未能保存,请查看原文。
在普通 EC2 上,浏览器虚拟机位于额外的 AWS hypervisor 层之上,因此页面错误可能跨越两层虚拟机。

从请求到可用的浏览器文章图表

当用户请求浏览器时,控制平面会选择一台有空闲容量的机器。该机器恢复一个已保存的浏览器虚拟机,在其中启动 Chromium,等待 Chromium 准备好被控制,然后返回一个连接 URL。

用户的智能体连接的就是这个 URL。Browser Use 通过 WebSocket 使用 Chrome DevTools Protocol(即 CDP)来控制 Chromium。CDP 是 Chrome 的远程控制 API:点击这个按钮、输入这段文本、读取这个页面、截取这张截图。

Five-step flow: pick host, resume VM from snapshot, launch Chromium, reach CDP-ready, return the connection URL for the agent to connect over CDP.:图片未能保存,请查看原文。
每个会话分五步:控制平面选择主机,虚拟机从快照恢复,Chromium 启动,一旦它准备好接受 CDP,智能体就通过 WebSocket 连接。

有三件事导致这个过程耗时更长:恢复虚拟机的内存、启动 Chromium,以及让浏览器保持隐蔽、不被反机器人安全机制检测到。

第一个减速点:内存文章图表

第一个瓶颈是内存。

生产环境的浏览器不是从零启动的。我们从快照恢复它:一个已保存的虚拟机,它已经启动并暂停在 Chromium 启动之前。恢复虚拟机比启动虚拟机要快得多。

我们的第一批恢复方案仍然太慢。当恢复的虚拟机首次访问内存时,主机必须将该内存重新映射回来。这一事件称为缺页异常。在嵌套虚拟机中,每次缺页异常的开销都很大,因为它可能跨越两层虚拟机。

在一次早期冷启动中,缺页异常占所有虚拟机退出的72%。从恢复到浏览器准备好CDP,耗时9.8秒。

修复方法是按更大的块映射内存。之前,虚拟机按4KB页恢复内存。现在,它使用2MB页。每页覆盖的内存多512倍,因此浏览器在唤醒过程中触发的缺页异常要少得多。缺页异常减少意味着穿越嵌套虚拟机层的次数减少。

Bar chart: resume-to-browser-ready drops from 9.8 seconds with 4KB pages to 3.1 seconds with 2MB pages, with missing-memory pauses falling from about 100,000 to about 1,100 per resume:图片未能保存,请查看原文。
按2MB页映射内存使浏览器启动更快。

We also now handle page faults ourselves with a custom handler for userfaultfd, a Linux API for handling missing memory pages. Before the VM starts running, our handler loads the memory Chromium is most likely to access first.

我们的处理器防止Chromium在启动时收到大量缺页异常。主机已经加载了热点页面,其余页面会在浏览器需要它们之前到达。

这些更改将从恢复虚拟机到浏览器准备好接受命令的时间从9.8秒缩短到3.1秒。它们还将浏览器虚拟机必须暂停并请求主机处理缺失内存的次数从每次恢复约100,000次减少到约1,100次,下降约91倍。

我们还做了较小的优化。虚拟机花费500ms寻找一个不存在的旧PS/2键盘。我们禁用了此检查。

此外,我们改变了主机等待浏览器就绪的方式。之前,主机不断用HTTP请求轮询虚拟机。这产生了额外的虚拟机退出,即浏览器虚拟机必须暂停以便主机为其处理工作的时刻。

Now, the browser driver writes a ready message to its log, and the host reads that log over vsock, a fast communication channel between the host and the VM. The host sees the ready message in under a millisecond.

第二个减速点:Chromium启动文章图表

下一个瓶颈是CPU。

当Chromium启动时,它既贪婪又苛刻。它同时创建渲染器、合成器和V8隔离区。之后,浏览器自动化要安静得多。智能体点击、等待、读取、再次点击。

由于Chromium启动后更安静,我们可以将许多浏览器打包到同一实例中。单个主机可以容纳许多浏览器,因为浏览器大部分时间都在等待:等待页面、网络响应或下一个智能体操作。

A grid of small squares representing browser VMs running on one host:图片未能保存,请查看原文。
打包在单个常规EC2主机上的浏览器虚拟机。

我们分两个阶段处理启动突发。当浏览器恢复且Chromium启动时,我们让其虚拟CPU不固定。这意味着Linux可以将浏览器的CPU工作在主机上移动,而不是将其锁定到固定核心。这分散了突发负载。

一旦浏览器报告就绪,我们就将这些虚拟CPU固定到稳定的核心。这意味着浏览器虚拟机现在在特定核心上运行。稳定的放置使我们能够在同一主机上打包更多浏览器,而无需猜测。我们知道哪些核心被占用,哪些仍有空间,以及哪些浏览器可能相互干扰。

启动阶段就像让人群通过每一扇开着的门进入。一旦所有人都进入,分配的座位就更有效。

从一开始就固定会使情况更糟。当许多浏览器同时启动时,它们堆积在相同的热点核心上,导致一些启动失败。

我们还对超线程变得格外谨慎。一个物理 CPU 核心通常会表现为两个逻辑 CPU,称为兄弟线程。这些兄弟线程仍然共享同一个物理核心。如果两个浏览器虚拟机各得到一个兄弟线程,它们就会争夺同一个核心。在嵌套环境下,这种争用表现为启动失败。为防止这种情况,现在每个浏览器都会获得它所使用的物理核心的两个兄弟线程。

接下来,我们为每个固定的 vCPU 线程赋予实时优先级。这会告诉 Linux,当浏览器虚拟机需要 CPU 时立即运行它,而不是让它排在不太重要的工作之后。在这一改动之前,一项 1,000 个浏览器的测试在会话创建后不久就损失了 17%。改动之后,同样的测试损失为零。

Two phases: during Chromium startup vCPUs are unpinned and the launch burst spreads across cores; once the browser is ready, vCPUs are pinned to stable cores:图片未能保存,请查看原文。
在 Chromium 启动的高峰期,vCPU 保持不固定,以便工作负载分散到整个主机上;一旦浏览器就绪,再将它们固定到稳定的核心上。

无屏幕下的隐身文章图表

最后一个瓶颈是隐身。

无头浏览器在没有可见窗口的情况下运行。有头浏览器则像你笔记本电脑上的浏览器一样运行,带有窗口、图形和渲染的帧。

普通的无头 Chromium 很容易被具有反机器人措施的网站检测到。根据我们的隐身基准测试,普通的无头 Chromium 只有 2% 的时间能避免被网站封禁。同一个 Chromium,以有头模式运行并带有可见窗口,仅通过渲染内容就能在 50% 的时间里避免封禁。

这就是为什么大多数供应商运行有头浏览器。他们为一个显示服务器、一个 GPU 和一个为没人观看的屏幕绘制帧的合成器付费。

我们完全以无头方式运行浏览器。这之所以可能,是因为我们修改了浏览器本身。

第一个组件是我们的Chromium 分支。许多隐身工具通过在浏览器启动后向每个页面注入 JavaScript 来隐藏自动化痕迹。例如,它们会覆盖浏览器属性,比如 navigator.webdriver,这是一个告诉网站浏览器是否正被自动化控制的标志,这样页面看到的就是 false 而不是 true。网站通常能检测到这些值是否被覆盖。为了避免这种情况,我们在 Chromium 的最底层进行修补,这样我们的补丁从一开始就不会暴露。

第二个组件是我们的指纹。浏览器指纹由网站读取的关于你的浏览器和机器的详细信息组成,包括你的操作系统、屏幕尺寸、字体、图形、输出、音频、时区、语言以及数百个其他细节。检测机器人的系统会检查这些细节看起来像真实用户的浏览器还是伪造的自动化环境。我们在 macOS、Windows 和 Linux 上使用了数万个真实指纹。

我们的浏览器在我们的隐身基准测试中81% 的时间避免了封禁,在 Halluminate BrowserBench 上为 84.8%,是所有供应商中最高的。由于没有显示,浏览器运行成本更低,也更容易扩展。

Horizontal bar chart of stealth benchmark block-avoidance rates: Browser Use 81%, Anchor 77%, kernel 67%, Browserless 54%, Steel 47%, Browserbase 42%, Hyperbrowser 40%, plain headless 2%.:图片未能保存,请查看原文。
我们隐身基准测试上的封禁规避率。我们的全无头浏览器以 81% 领先所有供应商。

连接到正确的浏览器文章图表

一旦浏览器就绪,用户通过 CDP 连接到它。公开 URL 是一个 WebSocket URL。

在浏览器集群前面是简单的边缘路由器。路由器接收 WebSocket 连接,询问控制平面该浏览器位于何处,然后将原始 CDP 字节转发到正确的虚拟机。

路由器不决定浏览器在哪里运行。如果一个路由器宕机,另一个路由器可以接管新的连接。控制平面负责放置。路由器只负责搬运字节。

成果文章图表

你的每个浏览器会话都由一个从快照恢复的微型虚拟机组成,该虚拟机运行在常规 EC2 内部,其内部运行着无头 Chromium。

虚拟机冷启动时间低于 400 毫秒。端到端来看,通过公共 API,浏览器创建延迟在 p50 时为 825 毫秒,在 p99 时为 1.35 秒。我们在一次 10,000 个会话的压力测试中测量了这一数据,其中每个浏览器都成功启动。

BrowserArena value-score leaderboard: Browser Use ranks #1 with 100% reliability, 762ms latency, $0.02/hr and a 0.941 value score, ahead of Notte, KERNEL, Anchor Browser, Steel, Hyperbrowser, and Browserbase.
来自 BrowserArena 的独立排行榜将 Browser Use 排名第一,可靠性为 100%,价格为 $0.02/hr。

该基础设施上的浏览器现已上线。开始使用 Browser Use Cloud。

目前最大的剩余成本是 Chromium 本身。恢复后启动 Chromium 在 p50 时仍需约 545 毫秒。

因此,任何进一步的改进都必须来自浏览器本身。

下一步:跳过 Chromium 启动文章图表

目前,我们在 Chromium 启动之前对虚拟机进行快照。这使快照保持简单:每个浏览器都从同一个干净的点唤醒,然后自行启动 Chromium。

但 Chromium 启动现在是最大的剩余成本。下一步是在 Chromium 已经运行之后进行快照。这样,新会话就完全不必启动浏览器了。它唤醒时浏览器已经处于活动状态。

Two timeline bars: today a session resumes the VM then spends about 545ms launching Chromium; next, the session resumes with Chromium already alive and skips the launch, saving about 545ms.:图片未能保存,请查看原文。
每个会话花费约 545 毫秒来启动 Chromium。在 Chromium 已经运行之后进行快照将完全跳过这一步。

这很复杂,因为一个正在运行的浏览器拥有打开的设备、计时器、图形状态、网络状态和指纹状态。在我们冻结它之前,需要将所有这些东西置于安全状态。在我们恢复它之后,每个浏览器仍然需要看起来像它自己的浏览器,而不是上一个浏览器的克隆。

这就是我们接下来正在研究的内容。

最快的浏览器是你几乎不需要启动的浏览器。我们通过在不该运行的地方运行 Firecracker,将虚拟机启动时间控制在 400 毫秒以下。接下来,我们将让新会话在 Chromium 已经运行的情况下唤醒。

上述基础设施上的浏览器现已在 cloud.browser-use.com 上线。

阅读显示设置

调整正文排版

当前浏览器记住选择,应用于文章正文。手机上正文宽度以屏幕可用空间为限。

图表完整性提示

普通文章遇到图表保存问题时显示提示。