@browser_use: https://x.com/browser_use/status/2066911791360422071

X AI KOLs Following 产品

摘要

Browser Use Cloud 使用 Firecracker 重建基础设施,将浏览器会话成本从每小时 0.06 美元降至 0.02 美元,同时实现亚秒级启动时间,并保持隔离性和可扩展性。

https://t.co/oOgVyS6oZu
查看原文
查看缓存全文

缓存时间: 2026/06/16 17:39

我们如何让云浏览器速度提升3倍、成本降低3倍

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

我们的云浏览器需要同时做到三件事:快速启动、保持隔离、成本低廉。正因如此,我们重构了Browser Use Cloud,使得新会话在一秒内启动,每个浏览器每小时成本从0.06美元降至0.02美元。

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

通常的解决方案是使用虚拟机。虚拟机是计算机内部的计算机:它拥有自己的CPU、内存、磁盘和网络设备。它与宿主机上的其他一切隔离,如果浏览器崩溃、泄露信息或遭受攻击,损害会被限制在虚拟机内部。

然而,对于云浏览器来说,普通虚拟机过于笨重。我们需要不断创建它们,有时一次创建数千个,并在会话结束后立即销毁。如果每个浏览器都需要一个缓慢、昂贵的虚拟机,那么整个产品也会变得缓慢和昂贵。

我们面临的问题是:能否在不让用户等待或付费的情况下,为每个浏览器提供自己的虚拟机?现在我们通过Firecracker——一个轻量级虚拟机系统——实现了这一点。

每个Browser Use Cloud会话都运行在自己专属的小型虚拟机中。这些虚拟机运行在EC2(亚马逊的云服务器租赁服务)上。

这正是与众不同的地方。Firecracker通常运行在裸金属服务器上,你需要租用整台物理机。为了降低客户成本,我们将其运行在普通EC2上,而AWS已经将你的服务器置于虚拟机内部。

这按理说会很慢。嵌套虚拟机会让内存和CPU操作的开销更大,而且Chromium启动也需要时间。本文将介绍我们如何让这种架构变得快速且高效。

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

为何我们放弃Unikernel

难以同时做到快速、隔离和廉价。

我们过去使用Unikraft运行云浏览器,它会构建称为unikernel的小型类似虚拟机的容器。Unikernel不是启动完整的Linux系统,而是加载一个为特定用途构建的小型镜像。Unikernel启动迅速,且空闲时成本低廉,因为无人使用时可以将其关闭。

Unikraft在非活跃时关闭浏览器方面表现良好,但在流量突增时快速增加浏览器容量方面表现不佳。如果突然有更多用户同时请求浏览器,就需要迅速扩展浏览器容量。Unikraft没有良好的内置自动扩缩功能,因此工程师必须手动修改变量,增加实例。

在流量爆发期间,系统无法自动反应,需要人工调整。这导致了问题:一次负载测试导致生产环境宕机45分钟。因此,我们在Firecracker上重建了系统。

Unikraft需要工程师手动增加容量,因此在流量峰值时滞后并崩溃。重建后,容量会自动跟踪需求。

Firecracker提供了一个可以创建、监控和运行虚拟机的层。它为每个虚拟机分配CPU、内存、磁盘和网络设备,并使其与宿主机及其他虚拟机隔离。

教会浏览器自我扩缩

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

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

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

它实时检查整个集群。这比依赖CloudWatch(AWS的监控服务)快得多,后者通常以一分钟为窗口做出反应。它还能知道通用指标无法获知的信息:仍在启动中的浏览器、我们正尝试移除的机器,以及不应接收新会话的机器。

请求从用户代码流经无状态边缘路由器;控制平面选择有空闲的EC2主机,并持续排空主机。

为何在虚拟机内部运行虚拟机

一旦我们有了控制平面,下一个问题就是它应该添加哪种类型的机器。

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

我们选择了普通EC2。普通EC2机器获取更快,维护成本更低。我们的宿主机从预构建镜像启动,并在启动后约30秒内开始提供浏览器服务。我们添加宿主机越快,需要支付的空闲容量就越少,传递给客户的成本也就越低。

但问题在于,普通EC2本身就是一个虚拟机。AWS在它自己的隔离层内运行我们的宿主机,然后我们再在那个宿主机内运行浏览器虚拟机。换句话说,每个浏览器都是一个运行在虚拟机内部的虚拟机。

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

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

在普通EC2上,浏览器虚拟机位于额外的AWS虚拟机监控器层之上,因此缺页错误可能跨越两个虚拟机层。

从请求到可用浏览器

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

这个URL是用户代理连接的对象。Browser Use通过Chrome DevTools Protocol(CDP)通过WebSocket控制Chromium。CDP是Chrome的远程控制API:点击按钮、输入文本、读取页面、截取屏幕截图。

每个会话分为五个步骤:控制平面选择宿主机,虚拟机从快照恢复,Chromium启动,一旦CDP就绪,代理通过WebSocket连接。

有三件事使这个过程变慢:恢复虚拟机内存、启动Chromium、以及保持浏览器隐身且不被反机器人安全机制检测到。

第一个瓶颈:内存

第一个瓶颈是内存。

生产环境的浏览器并非从头启动。我们是根据快照恢复它:一个已启动并暂停在Chromium启动前的已保存虚拟机。恢复虚拟机比启动快得多。

但我们的首次恢复仍然太慢。当恢复的虚拟机首次访问内存时,宿主机必须将该内存映射回去。这个事件称为缺页错误。在嵌套虚拟机中,每次缺页错误都很昂贵,因为它可能跨越两个虚拟机层。

在早期的冷启动中,缺页错误占所有虚拟机退出的72%。从恢复到CDP就绪的浏览器需要9.8秒。

解决方案是以更大的块映射内存。之前,虚拟机以4KB页恢复内存。现在,它使用2MB页。每页覆盖的内存大小是以前的512倍,因此浏览器在唤醒时触发的缺页错误大大减少。更少的缺页错误意味着更少地穿越嵌套虚拟机层。

以2MB页映射内存使浏览器启动更快。

我们还通过一个针对userfaultfd(用于处理缺失内存页的Linux API)的自定义处理程序自行处理缺页错误。在虚拟机开始运行之前,我们的处理程序会加载Chromium最可能首先访问的内存。

我们的处理程序防止Chromium在启动时收到大量缺页错误。宿主机已经加载了热页,剩余页面在浏览器需要大部分内存之前就已经到达。

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

我们还做了一些小的改进。虚拟机花费了500毫秒寻找一个不存在的旧PS/2键盘。我们禁用了这个检查。

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

现在,浏览器驱动将其就绪消息写入日志,宿主机通过vsock(宿主机与虚拟机之间的快速通信通道)读取该日志。宿主机在不到一毫秒内看到就绪消息。

第二个瓶颈:Chromium启动

下一个瓶颈是CPU。

当Chromium启动时,它资源消耗巨大且要求苛刻。它会同时创建渲染器、合成器和V8隔离区。之后,浏览器自动化就安静多了。代理点击、等待、读取、再次点击。

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

打包在单个普通EC2宿主机上的浏览器虚拟机。

我们分两个阶段处理启动爆发。当浏览器恢复且Chromium启动时,我们让它的虚拟CPU保持未绑定状态。这意味着Linux可以将浏览器的CPU工作分散到整个宿主机上,而不是将其锁定到固定核心。这有助于分散爆发负载。

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

启动阶段就像让一群人通过所有敞开的门进入。一旦所有人都在里面,分配座位效果更好。

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

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

最后,我们为每个固定的vCPU线程赋予实时优先级。这告诉Linux在浏览器虚拟机需要CPU时立即运行它,而不是将其排在次要任务之后。在更改之前,一个1000浏览器的测试在创建后不久就损失了17%的会话。更改后,同样的测试损失为零。

在Chromium启动爆发期间,vCPU保持未绑定状态,以便工作分散到整个宿主机;然后浏览器就绪后,vCPU被固定到稳定核心。

无屏幕隐身

最后一个瓶颈是隐身。

无头浏览器运行时不显示窗口。有头浏览器运行时则像你笔记本电脑上的浏览器一样,带有窗口、图形和渲染帧。

普通的无头Chromium容易被有反机器人措施的网站检测到。根据我们的隐身基准测试,普通无头Chromium被网站拦截的规避率只有2%。同样的Chromium,以有头模式运行并显示窗口,仅通过渲染内容就能避免50%的拦截。

这就是为什么大多数提供商运行有头浏览器。他们需要为显示服务器、GPU和合成器付费,这些组件为没人看的屏幕绘制帧。

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

第一项是我们的Chromium分支。许多隐身工具通过在浏览器启动后向每个页面注入JavaScript来隐藏自动化。例如,他们覆盖浏览器属性如navigator.webdriver(该标志告诉网站浏览器是否被自动化控制),使页面看到false而非true。网站通常可以通过检查浏览器对象是否仍像正常Chrome一样行为来检测这些覆盖。我们则在C++和操作系统层面直接修补Chromium本身,因此这些自动化信号从一开始就不会暴露。

第二项是我们的指纹层。浏览器指纹是网站可以读取的关于浏览器和机器的详细信息集合:操作系统、屏幕尺寸、字体、图形输出、音频行为、时区、语言以及数百个较小的信号。反机器人系统检查这些细节是否看起来像真实用户的浏览器还是虚假的自动化环境。

我们的指纹层使用了来自生产环境流量(涵盖macOS、Windows和Linux)的数万个真实指纹。我们还使用基于CPU的渲染器而非物理GPU来渲染图形,因此浏览器可以在没有真实GPU或虚拟显示器的情况下生成一致的Canvas和WebGL输出。

这使我们能够实现隐身,同时仍然可以在每台机器上打包大量浏览器。在我们的隐身基准测试中,浏览器规避率为81%,在Halluminate BrowserBench上为84.8%,是任何提供商中最高的。由于没有显示器,浏览器运行成本更低,扩展更容易。

在我们的隐身基准测试中的拦截规避率。我们的完全无头浏览器以81%领先所有提供商。

连接到正确的浏览器

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

在浏览器集群前端是简单的边缘路由器。路由器获取WebSocket连接,向控制平面询问该浏览器所在位置,并将原始CDP字节转发到正确的虚拟机。

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

结果

你的每个浏览器会话由一个小型虚拟机组成,该虚拟机从快照恢复,运行在普通EC2内部,内部运行无头Chromium。

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

来自BrowserArena的独立排行榜将Browser Use评为第一,可靠性100%,成本$0.02/小时。

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

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

下一步:跳过Chromium启动

目前,我们在Chromium启动前拍摄虚拟机快照。这保持了快照的简单性:每个浏览器从相同的、干净的点唤醒,然后自行启动Chromium。

但现在Chromium启动是最大的剩余成本。下一步是在Chromium已经运行后拍摄快照。这样,新会话就不必启动浏览器了。

相似文章