19-性能调优与扩展
19-性能调优与扩展

19-性能调优与扩展

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% 考虑集群或清理

相关文档

相关条目

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注