5-系统架构设计
5-系统架构设计

5-系统架构设计

Judge0 是一个生产级别的开源在线代码执行系统,采用分层架构设计,核心由 Ruby on Rails API 服务、Resque 后台任务队列、Isolate 沙箱执行环境以及 PostgreSQL 和 Redis 数据存储层组成。整个系统遵循前后端分离原则,通过 RESTful API 接收代码提交请求,由独立的工作进程负责在隔离环境中执行用户代码。

技术栈概览

Judge0 基于以下核心技术构建,每个组件都承担着特定职责,共同支撑起整个代码执行平台。

层级 技术选型 职责说明
Web 框架 Ruby on Rails 6.1 (API-only) 接收 HTTP 请求,提供 REST API
任务队列 Resque + Resque-Scheduler 管理后台代码执行任务
缓存/消息队列 Redis 7.2.4 存储任务队列、缓存提交结果
数据库 PostgreSQL 16.2 持久化提交记录、语言配置
沙箱隔离 Isolate Linux 命名空间隔离执行环境
序列化 ActiveModel Serializers JSON 格式数据转换
容器编排 Docker Compose 服务部署与扩缩容

Sources: Gemfile, docker-compose.yml

多服务架构

Judge0 采用 Docker Compose 部署模式,将系统拆分为四个独立服务,实现关注点分离与水平扩展能力。

graph TB
    subgraph Docker_Network
        subgraph API_Service["API 服务 (Rails)"]
            RC[SubmissionsController]
            SS[SubmissionSerializer]
            BS[Base64Service]
        end

        subgraph Worker_Service["Worker 服务 (Resque)"]
            IJ[IsolateJob]
            IR[IsolateRunner]
            IS[Isolate Sandbox]
        end

        PG[(PostgreSQL<br/>数据库)]
        RD[(Redis<br/>任务队列)]
    end

    User([用户请求]) --> API_Service
    API_Service --> PG
    API_Service --> RD
    RD --> Worker_Service
    Worker_Service --> IS
    Worker_Service --> PG

服务职责划分:

  • Server 服务:运行 Rails 应用,处理 HTTP 请求,将代码提交任务入队到 Redis
  • Worker 服务:运行 Resque 工作进程,从 Redis 消费任务,调用 Isolate 执行代码
  • PostgreSQL:存储提交记录、语言配置、用户认证信息
  • Redis:作为 Resque 后端存储任务队列

Sources: docker-compose.yml, scripts/server, scripts/workers

请求处理流程

当用户提交代码时,系统遵循严格的异步处理流程,确保高并发场景下的稳定性。

sequenceDiagram
    participant U as 用户
    participant API as Rails API
    participant Redis as Redis Queue
    participant Worker as Worker 进程
    participant PG as PostgreSQL
    participant IS as Isolate 沙箱

    U->>API: POST /submissions
    API->>PG: 创建 Submission 记录
    API->>Redis: 入队 IsolateJob
    API-->>U: 返回 token (202 Created)

    Worker->>Redis: 获取 IsolateJob
    Worker->>Worker: 初始化工作目录
    Worker->>IS: 编译代码
    IS-->>Worker: 编译结果

    alt 编译成功
        Worker->>IS: 执行代码
        IS-->>Worker: 执行结果 (stdout/stderr/time/memory)
        Worker->>PG: 更新 Submission 状态
    else 编译失败
        Worker->>PG: 更新为 CE 状态
    end

    U->>API: GET /submissions/{token}
    API->>PG: 查询 Submission
    API-->>U: 返回执行结果

同步等待模式:wait=true 参数可启用同步等待,控制器会轮询任务状态直至完成,适合需要即时获取结果的场景。

Sources: submissions_controller.rb, isolate_runner.rb

核心模块交互

SubmissionsController 控制器

控制器负责请求验证、参数解析和任务分发,是系统的 API 网关。

# 关键职责:
# 1. 参数白名单过滤 (submission_params)
# 2. Base64 编解码处理
# 3. 同步/异步执行模式切换
# 4. 批量提交支持

主要动作包括 create(创建提交)、show(查询单个结果)、index(分页列表)、batch_create(批量创建)和 batch_show(批量查询)。

Sources: submissions_controller.rb

IsolateJob 后台任务

IsolateJob 继承自 ApplicationJob,使用 Resque 作为队列适配器,负责在沙箱中执行代码。

class IsolateJob < ApplicationJob
  retry_on RuntimeError, wait: 0.1.seconds, attempts: 100

  queue_as ENV["JUDGE0_VERSION"].to_sym

  def perform(submission_id)
    # 1. 初始化工作目录 (isolate --init)
    # 2. 编译代码 (如果需要)
    # 3. 执行代码 (可多次运行取平均值)
    # 4. 验证输出结果
    # 5. 清理沙箱
  end
end

任务队列名称基于 JUDGE0_VERSION 环境变量,支持多版本隔离。自动重试机制确保临时故障不会丢失任务。

Sources: isolate_job.rb

IsolateRunner 任务调度器

IsolateRunner 提供同步/异步两种任务调度方式。

module IsolateRunner
  def self.perform_now(submission)
    # 1. 立即入队任务
    # 2. 轮询查询状态 (指数退避策略)
    # 3. 阻塞等待结果 (最长 600 秒)
  end

  def self.perform_later(submission)
    # 仅入队任务,立即返回 token
  end
end

轮询策略采用指数退避:2s → 1s → 0.5s → 1s → 1.5s → ...

Sources: isolate_runner.rb

数据模型层

Submission 模型:核心实体,包含代码、输入输出、执行结果等完整信息。

class Submission < ApplicationRecord
  # 自动 Base64 编解码存储
  def source_code
    @decoded_source_code ||= Base64Service.decode(self[:source_code])
  end

  # 关联语言配置
  def language
    @language ||= Language.unscoped.find_by(id: language_id)
  end

  # 枚举状态
  enumeration :status
end

Language 模型:语言配置,定义编译命令和运行命令。

class Language < ApplicationRecord
  validates :name, presence: true
  validates :source_file, :run_cmd, presence: true, unless: -> { is_project }
  default_scope { where(is_archived: false).order(name: :asc) }
end

Sources: submission.rb, language.rb

状态枚举系统

使用自定义枚举类管理提交状态,提供清晰的状态流转语义。

class Status < Enumerations::Base
  values queue:     { id:  1, name: 'In Queue' },
         process:   { id:  2, name: 'Processing' },
         ac:        { id:  3, name: 'Accepted' },
         wa:        { id:  4, name: 'Wrong Answer' },
         tle:       { id:  5, name: 'Time Limit Exceeded' },
         ce:        { id:  6, name: 'Compilation Error' },
         sigsegv:   { id:  7, name: 'Runtime Error (SIGSEGV)' },
         sigxfsz:   { id:  8, name: 'Runtime Error (SIGXFSZ)' },
         sigfpe:    { id:  9, name: 'Runtime Error (SIGFPE)' },
         sigabrt:   { id: 10, name: 'Runtime Error (SIGABRT)' },
         nzec:      { id: 11, name: 'Runtime Error (NZEC)' },
         other:     { id: 12, name: 'Runtime Error (Other)' },
         boxerr:    { id: 13, name: 'Internal Error' },
         exeerr:    { id: 14, name: 'Exec Format Error' }
end

Sources: status.rb

配置管理体系

Judge0 采用环境变量驱动配置,通过 judge0.conf 文件集中管理所有可配置项。

graph LR
    CF[judge0.conf] -->|source| LC[load-config]
    LC -->|export| ENV[环境变量]
    ENV -->|读取| Rails[Rails 应用]
    ENV -->|读取| Worker[Worker 进程]

    subgraph 配置分类
        SC[服务器配置]
        WC[Worker 配置]
        AC[认证配置]
        RC[资源限制]
    end

核心配置项分类:

分类 关键配置 默认值
服务器 MAINTENANCE_MODE, ENABLE_WAIT_RESULT false, true
Worker COUNT, MAX_QUEUE_SIZE, INTERVAL 2*nproc, 100, 0.1s
资源限制 CPU_TIME_LIMIT, MEMORY_LIMIT 5s, 128000KB
认证 AUTHN_HEADER, AUTHZ_HEADER X-Auth-Token, X-Auth-User

Sources: config.rb, judge0.conf

数据库架构

erDiagram
    SUBMISSION {
        int id PK
        text source_code
        int language_id FK
        text stdin
        text expected_output
        text stdout
        int status_id
        decimal time
        int memory
        string token UK
        datetime created_at
        datetime finished_at
    }

    LANGUAGE {
        int id PK
        string name
        string compile_cmd
        string run_cmd
        string source_file
        boolean is_archived
    }

    SUBMISSION ||--o{ LANGUAGE : "references"

数据库采用 PostgreSQL,支持复合索引优化查询性能,token 字段建立唯一索引加速结果查询。

Sources: schema.rb

安全隔离机制

代码执行安全由 Isolate 沙箱保证,通过 Linux cgroups 和命名空间实现进程级隔离。

# Isolate 执行命令示例
isolate \
  -s \                    # 安全模式
  -b ${box_id} \         # 沙箱 ID
  -M ${metadata_file} \  # 元数据输出
  -t ${cpu_time} \       # CPU 时间限制
  -m ${memory} \         # 内存限制
  -k ${stack_size} \     # 栈大小限制
  -p${max_procs} \       # 最大进程数
  --cg \                 # 启用 cgroups
  --run -- /bin/bash run.sh

隔离策略:

  • 资源限制:CPU 时间、内存、栈大小、文件大小、进程数
  • 网络控制:--share-net 选项控制网络访问
  • 环境变量:仅允许预定义环境变量传递
  • 文件系统:/etc 目录设置 noexec 防止执行

Sources: isolate_job.rb, judge0.conf

序列化与缓存

ActiveModel Serializers 负责 JSON 序列化,支持按字段过滤和 Base64 编码。

class SubmissionSerializer < ActiveModel::Serializer
  def self.default_fields
    [:token, :time, :memory, :stdout, :stderr, :compile_output, :message, :status]
  end

  def source_code
    base64_encoded ? object[:source_code] : object.source_code
  end
end

Rails 缓存:查询结果使用 Rails.cache.fetch 缓存,默认 1 秒过期,通过 race_condition_ttl 防止缓存击穿。

Sources: submission_serializer.rb, submissions_controller.rb

部署架构

开发与生产环境采用不同的 Docker Compose 配置。

graph TB
    subgraph Development
        J0D[judge0:latest-dev] <--> DBD[PostgreSQL]
        J0D <--> RDD[Redis]
    end

    subgraph Production
        API[Server 容器] <--> DBP[PostgreSQL]
        API <--> RDP[Redis]
        W1[Worker 1] <--> RDP
        W2[Worker 2] <--> RDP
        Wn[Worker n] <--> RDP
    end

Worker 数量默认设置为 2 * CPU核心数,可通过 COUNT 环境变量调整。

Sources: docker-compose.dev.yml, scripts/workers

相关文档

相关条目

发表回复

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