Judge0 作为高性能在线代码执行系统,其性能表现取决于多个层面的协同优化:任务调度机制、资源限制配置、水平扩展能力以及数据库访问效率。本文系统阐述性能调优的核心参数、扩展策略与最佳实践。
系统性能架构概览
Judge0 的性能架构由三大核心组件构成,它们协同工作以实现高吞吐量的代码执行:
graph TB
subgraph "API 层 (Rails + Puma)"
A["SubmissionsController<br/>请求接收"] --> B["参数验证与转换"]
B --> C["Rails Cache 查询"]
end
subgraph "任务队列层 (Resque + Redis)"
D["IsolateRunner<br/>任务分发"] --> E["Redis Queue<br/>任务队列"]
E --> F["Resque Worker<br/>任务消费"]
end
subgraph "执行层 (Isolate Sandbox)"
F --> G["IsolateJob<br/>沙箱执行"]
G --> H["Isolate Sandbox<br/>代码运行"]
H --> I["元数据收集<br/>时间/内存/状态"]
end
C -->|同步等待| D
D -->|异步入队| E
G -->|读取提交| J["PostgreSQL<br/>结果存储"]
I --> J
请求处理流程:客户端通过 API 创建提交后,系统支持两种执行模式——同步等待模式 (wait=true) 会阻塞连接直至执行完成,异步模式则立即返回 token,客户端通过轮询或回调获取结果。
Sources: submissions_controller.rb, isolate_runner.rb
核心性能参数体系
队列与并发配置
队列配置是影响系统吞吐量的首要因素。MAX_QUEUE_SIZE 定义队列最大积压量,当队列满时新提交将被拒绝:
# judge0.conf
MAX_QUEUE_SIZE=100 # 队列最大积压量
INTERVAL=0.1 # Worker 轮询间隔(秒)
COUNT=4 # 并行 Worker 数量,默认 2*nproc
| 参数 | 默认值 | 调优建议 |
|---|---|---|
MAX_QUEUE_SIZE |
100 | 根据 Worker 数量和平均执行时间调整;8 核系统建议 200-400 |
INTERVAL |
0.1s | 降低可减少响应延迟,但会增加 Redis 负载 |
COUNT |
2×CPU核数 | 每个 Worker 可同时执行一个提交;CPU 密集型任务建议等于核数 |
Sources: judge0.conf, scripts/load-config
Rails 服务器配置
Puma 多进程多线程架构决定了 API 层的并发处理能力:
# config/puma.rb
threads_count = Integer(ENV['RAILS_MAX_THREADS']) # 默认: nproc
process_count = Integer(ENV['RAILS_SERVER_PROCESSES']) # 默认: 2
# judge0.conf
RAILS_MAX_THREADS=8 # 每个进程的线程数
RAILS_SERVER_PROCESSES=2 # 进程数
连接池配置需要与进程/线程配置匹配:
# config/database.yml
pool: <%= [1, ENV["RAILS_SERVER_PROCESSES"].to_i * ENV["RAILS_MAX_THREADS"].to_i].max %>
配置示例:4 进程 × 8 线程 = 32 连接池大小
Sources: puma.rb, database.yml, load-config
执行资源限制
代码执行资源限制直接影响单个提交的性能和系统整体稳定性:
# judge0.conf
# 时间限制
CPU_TIME_LIMIT=5 # 默认 CPU 时间限制(秒)
MAX_CPU_TIME_LIMIT=15 # 用户可设置的最大值
CPU_EXTRA_TIME=1 # 超时后额外等待时间
# 内存限制
MEMORY_LIMIT=128000 # 默认内存限制(KB),约 125MB
MAX_MEMORY_LIMIT=512000 # 最大允许值,约 500MB
# 进程限制
MAX_PROCESSES_AND_OR_THREADS=60 # 最大进程/线程数
MAX_MAX_PROCESSES_AND_OR_THREADS=120
| 限制类型 | 软限制 | 硬限制 | 适用场景 |
|---|---|---|---|
| CPU 时间 | 5s | 15s | 普通题目 |
| 内存 | 128MB | 512MB | 算法题/大数组 |
| 进程数 | 60 | 120 | 多线程程序 |
Sources: judge0.conf, isolate_job.rb
水平扩展架构
Docker Compose 多实例部署
水平扩展通过增加 Worker 容器数量实现:
# docker-compose.yml
services:
server:
image: judge0/judge0:latest
ports:
- "2358:2358"
# API 服务通常 1-2 个实例
worker:
image: judge0/judge0:latest
command: ["./scripts/workers"]
# 根据负载增加 worker 副本数
deploy:
replicas: 4
worker-2: # 自定义 Worker 配置
image: judge0/judge0:latest
command: ["./scripts/workers"]
environment:
- COUNT=8 # 更多并行 Worker
# 扩展 Worker 到 8 个
docker-compose up -d --scale worker=8
Sources: docker-compose.yml
Worker 进程管理
Worker 启动脚本实现了自动重启和监控机制:
sequenceDiagram
participant Main as Worker 进程
participant Scheduler as Resque Scheduler
participant Jobs as Worker Jobs
Main->>Main: 创建 tmp/pids 目录
loop 监控循环
Main->>Scheduler: 启动 resque:scheduler
Main->>Jobs: 启动 resque:workers
Jobs->>Jobs: 执行提交任务
Note over Jobs: 接收 SIGTERM 时优雅退出
Jobs->>Main: 进程退出
Main->>Main: 重新启动 Workers
end
Sources: scripts/workers
分布式追踪字段
系统自动记录任务在不同主机的执行情况,便于负载分析和故障定位:
| 字段 | 用途 | 来源 |
|---|---|---|
queue_host |
入队时的主机名 | IsolateRunner.perform_later |
execution_host |
实际执行的主机名 | IsolateJob.perform |
queued_at |
入队时间戳 | 任务入队时刻 |
started_at |
开始执行时间 | IsolateJob 读取 |
# 追踪信息记录
submission.update(
status: Status.process,
started_at: DateTime.now,
execution_host: ENV["HOSTNAME"]
)
Sources: isolate_job.rb, isolate_runner.rb
数据库性能优化
索引策略
Submissions 表包含关键索引以支持高效查询:
# db/migrate/20170203021217_add_index_to_submission.rb
add_index :submissions, :token # 用于快速 token 查询
# app/models/submission.rb
default_scope { order(created_at: :desc) } # 默认按创建时间排序
| 查询场景 | 优化方式 |
|---|---|
| Token 精确查询 | token 索引 |
| 分页列表 | created_at 复合索引 |
| 状态轮询 | status_id 索引(隐式) |
Sources: schema.rb, submission.rb
提交缓存机制
show 接口使用 Rails Cache 减少数据库压力:
def show
token = params[:token]
render json: Rails.cache.fetch("#{token}",
expires_in: Config::SUBMISSION_CACHE_DURATION,
race_condition_ttl: 0.1*Config::SUBMISSION_CACHE_DURATION
) {
Submission.find_by!(token: token)
}, ...
end
# judge0.conf
SUBMISSION_CACHE_DURATION=1 # 缓存时间(秒),0 禁用缓存
缓存策略:刚完成的提交会被频繁轮询,1 秒缓存可显著减少数据库查询,同时保持结果时效性。
Sources: submissions_controller.rb, judge0.conf
Resque 队列配置
Redis 连接配置
# config/initializers/resque.rb
Resque.redis = Redis.new(
host: ENV["REDIS_HOST"],
port: ENV["REDIS_PORT"],
password: ENV["REDIS_PASSWORD"],
thread_safe: true # 确保线程安全
)
Resque.redis.namespace = ENV["RESQUE_NAMESPACE"] if ENV["RESQUE_NAMESPACE"].present?
| 配置项 | 默认值 | 说明 |
|---|---|---|
REDIS_HOST |
localhost/redis | 生产环境使用 Docker 网络名 |
REDIS_PORT |
6379 | Redis 端口 |
RESQUE_NAMESPACE |
无 | 多实例隔离时使用 |
Sources: resque.rb
队列大小监控
队列满时拒绝新提交,防止内存溢出:
def check_queue_size
number_of_submissions = params[:submissions].try(:size).presence || 1
if Resque.size(ENV["JUDGE0_VERSION"]) + number_of_submissions > Config::MAX_QUEUE_SIZE
render json: { error: "queue is full" }, status: :service_unavailable
end
end
Sources: submissions_controller.rb
执行效率优化
多次运行平均
NUMBER_OF_RUNS 参数支持单次提交多次执行并取平均值,减少结果抖动:
NUMBER_OF_RUNS=1 # 默认执行次数
MAX_NUMBER_OF_RUNS=20 # 最大允许次数
# isolate_job.rb
submission.number_of_runs.times do
initialize_workdir
compile == :failure ? break : run
time << submission.time
memory << submission.memory
cleanup
break if submission.status != Status.ac # 失败提前终止
end
submission.time = time.inject(&:+).to_f / time.size
submission.memory = memory.inject(&:+).to_f / memory.size
Sources: isolate_job.rb, judge0.conf
Sandbox 隔离优化
Isolate 沙箱配置对性能有直接影响:
# 使用 cgroups 进行精确资源控制
cgroups = "--cg" # 当需要限制内存或 CPU 时启用
# 批量操作优化
box_id = submission.id % 2147483647 # ID 取模复用沙箱
沙箱复用机制减少了 isolate --init 和 --cleanup 的系统调用开销。
Sources: isolate_job.rb
生产环境调优清单
资源配置推荐
| 场景 | CPU | 内存 | Worker 数 | 队列大小 |
|---|---|---|---|---|
| 开发/测试 | 2 核 | 2GB | 2 | 50 |
| 小型生产 | 4 核 | 4GB | 4 | 100 |
| 中型生产 | 8 核 | 8GB | 8 | 200 |
| 大型生产 | 16+ 核 | 16GB+ | 16+ | 400+ |
关键配置示例
# judge0.conf - 生产环境推荐配置
# 队列配置
MAX_QUEUE_SIZE=200
INTERVAL=0.05
COUNT=8
# Rails 配置
RAILS_MAX_THREADS=8
RAILS_SERVER_PROCESSES=4
# 数据库连接池
# POSTGRES_PASSWORD=<strong-password>
# 性能参数
CPU_TIME_LIMIT=10
MAX_CPU_TIME_LIMIT=30
MEMORY_LIMIT=256000
NUMBER_OF_RUNS=1
SUBMISSION_CACHE_DURATION=2
# docker-compose.prod.yml
services:
server:
deploy:
replicas: 2
environment:
- RAILS_SERVER_PROCESSES=4
- RAILS_MAX_THREADS=8
worker:
deploy:
replicas: 8
environment:
- COUNT=8
监控指标
建议监控以下关键指标:
| 指标 | 告警阈值 | 说明 |
|---|---|---|
| 队列深度 | > 150 | Worker 数量不足 |
| 平均响应时间 | > 5s | 资源限制过严或负载过高 |
| Worker CPU 使用率 | > 80% | 需要更多 Worker |
| Redis 内存 | > 70% | 考虑集群或清理 |
相关文档
- 系统架构设计 — 深入理解 Judge0 整体架构
- Docker Compose 服务架构 — 服务部署与网络配置
- 配置文件详解 — 所有配置项完整参考
- 安全机制与沙箱隔离 — 资源隔离原理
相关条目
- 17-Docker Compose 服务架构
- 8-IsolateRunner 任务调度器
- 5-系统架构设计