Modal · 官方

数秒内扩展至百万级并发沙箱

Scaling to 1 million concurrent sandboxes in seconds

二〇二六年七月十七日 · 英文原文

我们构建了一个可在数秒内将单个工作空间的沙箱(sandbox)并发数扩展至100万个的调度系统。系统采用去中心化调度、分层资源池与预分配-即时激活设计,由调度协调器、节点代理和全局状态存储(etcd/Consul)组成。关键实现包括批量预留与惰性分配,将调度延迟从O(n)降至O(1);基于令牌桶的速率控制防止调度风暴;故障恢复可在数秒内完成重调度。测试环境(100节点,每节点256GB内存、64核CPU)下,100万沙箱调度延迟小于2秒,单个沙箱激活延迟小于50毫秒,系统吞吐量达每秒50万个调度请求。

我们如何(以及为何)构建一个可在数秒内扩展至(每个工作空间)100万个并发沙箱的调度系统

背景与动机

在构建大规模基础设施时,调度系统往往是瓶颈所在。我们面临的核心挑战是:如何让一个工作空间内的沙箱(sandbox)数量在几秒内从零扩展到100万个并发实例,同时保持资源利用率和响应速度。

传统的调度方案——无论是基于队列的批处理系统,还是依赖中心化调度器的架构——都无法满足这种弹性需求。它们要么在节点数量激增时产生不可接受的调度延迟,要么在资源碎片化问题上陷入困境。

设计原则

我们围绕三个核心原则构建了这个系统:

  1. 去中心化调度:没有单一调度器成为瓶颈。每个节点自主决策,通过分布式共识协调全局状态。
  2. 分层资源池:将资源按层级组织(工作空间 → 集群 → 节点 → 容器),每一层只维护必要的元数据,避免全局锁竞争。
  3. 预分配与即时激活:沙箱的创建分为两个阶段——资源预留(reservation)和实际激活(activation)。预留阶段在后台异步完成,激活阶段只需毫秒级操作。

架构概览

系统由三个主要组件构成:

关键实现细节

1. 批量预留与惰性分配

当收到创建100万个沙箱的请求时,协调器不会立即为每个沙箱分配具体资源。相反,它先向集群广播一个“预留请求”,每个节点根据自身空闲资源量响应一个承诺(promise)。协调器收集所有承诺后,在全局状态中记录预留总量,然后立即返回成功——此时沙箱尚未实际创建。

实际容器创建发生在沙箱被首次访问时(惰性分配)。节点代理在收到访问请求后,从本地预分配的 IP 池和存储卷中快速启动容器。这种设计将调度延迟从 O(n) 降低到 O(1)。

2. 基于令牌桶的速率控制

为了防止调度风暴,每个工作空间和每个节点都维护一个令牌桶(token bucket)。协调器在分发子任务前必须获取工作空间级别的令牌,节点在创建容器前必须获取节点级别的令牌。令牌的补充速率根据历史负载动态调整。

3. 故障恢复与重调度

节点故障时,其上的预留信息会超时失效。协调器定期扫描全局状态,发现失效预留后立即触发重新调度。由于预留阶段不占用实际资源,重调度可以在几秒内完成。

性能基准

在测试环境中(100个节点,每个节点 256GB 内存、64核 CPU),我们实现了以下指标:

为何选择这种设计

我们选择去中心化+惰性分配方案,而不是更常见的中心化调度器(如 Kubernetes 默认调度器),原因在于:

未来方向

当前系统仍有一些限制:例如,跨数据中心的调度尚未支持;节点间的资源均衡依赖简单的轮询策略。我们计划在后续版本中引入基于强化学习的调度优化,以及跨区域资源池的自动伸缩。


如果你对具体实现细节感兴趣,可以查看我们的开源调度库(链接),其中包含了核心调度算法的参考实现。

译自 Modal · 官方 · 录于 二〇二六年七月十七日