系统设计 Lab
Online Judge 的扩展主要是 worker 经济学,而不是 API 流量。
把滑块从一个玩具 judge 推到接近 LeetCode 的工作负载。当 compilation 和 sandbox 执行成为主导时,设计就变了:异步提交、把活儿排进 queue、预热 container、缓存不可变的结果,并把重型 submit 和轻量 run-code 流量拆开。
分步讲解
一步一步把它想清楚
要点
常规演进场景
从左到右点击,就是预设的演示路径。每张卡片都会改变 workload 输入。
推荐形态
当前架构路径
客户端
用户 提交代码,并轮询不可变的 verdict
API
API server 校验请求、存 metadata,并返回一个 token
Queueing
Queue 吸收尖峰,并给 worker 一个 pull-based 的 backlog
执行
Scheduler 施加 fairness、priority 和 queue 选择
Worker pool CPU 和内存吃重的 compilation 与执行
Warm runner 按语言划分的 sandbox container
存储 + 结果
Submission DB durable 的 append-only verdict 历史
Result cache 带 TTL 的廉价轮询读取
Object store 大块的源码、题目和 testcase blob
瓶颈
Worker 容量
Queue 压力
Result lookup
Sandbox pool
Submission 存储
为什么会变
决策权衡
有出处支撑的规则
这些是模型背后那些经得起时间考验的 system design 论断。而 slider 的具体阈值,则被刻意标注为教学用的假设。
container 的 resource limit 是执行 TLE 和 MLE 的单位
CPU 和内存 limit 给 worker 基础设施一个具体手段,去中止那些超出题目约束的 submission。
Docker DocsSeccomp 缩小了不可信代码的 syscall 攻击面
sandbox 应该拦截危险的 system call,而不是仅仅信任语言 runtime 或进程权限。
Docker Docsqueue 把 submit 流量和 worker 执行解耦
queue 吸收突发流量,让 worker 按自己的节奏消费,这正好契合异步判题。
AWS SQS Docs带 TTL 的 key-value 结果让轮询很廉价
最终 verdict 是不可变的,所以短轮询可以一直读一个小的缓存对象,直到这个 key 过期。
Redis Docs教学用假设
- worker slot 的阈值是教学阈值;真实容量取决于语言组合、testcase 大小、CPU 型号和隔离开销。
- result cache 存的是最终或进行中的 verdict 对象,不是源码 blob。
- 这里故意不要求 Kafka,除非设计需要 replay、分析 fanout 或多个 consumer。