Recodex:开源编程作业自动评测系统的架构、部署与实战指南
1. 项目概述:一个面向编程作业自动评测的开源利器
如果你是一名计算机科学或相关专业的教师,或者是一个技术培训机构的负责人,那么你肯定对“批改编程作业”这件事深有体会。这绝对是一项耗时、费力且容易出错的工作。你需要手动下载学生的代码,配置运行环境,逐个编译执行,然后对照测试用例检查输出结果,最后还要给出分数和反馈。一个班几十个学生,一次作业就能让你加班到深夜。而“adryfish/recodex”这个项目,就是为了将你从这种重复性劳动中彻底解放出来。
Recodex 是一个开源的、基于 Web 的编程作业自动评测系统。它的核心目标非常明确:自动化、标准化、规模化地处理编程作业的提交、评测与反馈。想象一下,学生通过一个简洁的网页界面提交他们的代码(支持多种编程语言),系统在后台的沙箱环境中自动编译、运行,并对照预设的测试用例进行比对,瞬间就能给出分数、运行时间、内存消耗等详细报告。教师则可以在管理后台轻松查看全班学生的成绩分布、常见错误,甚至进行代码相似度检测,以防范抄袭。
这个项目最初由捷克马萨里克大学(Masaryk University)的团队开发,用于支撑其大规模计算机科学课程的教学。adryfish 是其在 GitHub 上的一个镜像仓库。它不是一个简单的在线判题系统(Online Judge),而是一个功能更全面、更贴近实际教学流程的“作业管理+自动评测”一体化平台。对于任何有编程教学需求的场景——无论是高校课程、在线教育平台,还是企业内部技术培训——Recodex 都提供了一个成熟、可靠且可高度自定义的解决方案。
2. 核心架构与设计哲学解析
2.1 为什么是微服务架构?
Recodex 采用了典型的微服务架构,这并非偶然,而是由其核心需求决定的。一个自动评测系统需要处理多种异构的任务:用户认证、作业管理、代码执行、文件存储、结果计算等。这些任务对资源、安全性和扩展性的要求各不相同。
将系统拆分为多个独立的服务(如前端 API、评测后端、文件服务器、工作节点等),带来了几个关键优势:
- 独立部署与扩展:当提交作业的高峰期来临,可以单独横向扩展“评测后端”和“工作节点”实例,而无需动整个应用。前端访问量大了,就扩展 API 服务。
- 技术栈灵活性:不同的服务可以使用最适合其任务的技术。例如,评测后端对并发和性能要求高,可能用 Go 或 C++ 编写;而管理前端为了快速迭代,可能采用 React + Node.js。
- 故障隔离:文件服务器出现故障,不会导致整个评测功能瘫痪,系统可以降级运行或提示稍后重试。
- 团队协作清晰:清晰的服务边界有利于开发团队分工,每个团队可以专注于一个或几个服务的开发和维护。
这种设计使得 Recodex 能够从容应对成百上千名学生同时提交作业的并发压力,也为其未来的功能演进打下了坚实的基础。
2.2 安全第一:沙箱环境的设计考量
自动评测系统最核心、也最危险的部分,就是执行用户提交的未知代码。这段代码可能是无意的死循环,也可能是有意的恶意脚本(如尝试删除服务器文件、进行网络攻击等)。因此,沙箱(Sandbox)技术是 Recodex 的基石。
Recodex 的评测后端会为每一次代码运行创建一个高度隔离的沙箱环境。这个环境通常基于操作系统级别的虚拟化技术,如Linux Namespaces和Cgroups。
- Namespaces负责隔离:它为进程提供独立的视图,包括进程 ID、网络接口、文件系统挂载点、主机名等。在沙箱内的进程,看不到宿主机上的其他进程,也访问不到宿主机的真实文件系统(除非显式挂载)。
- Cgroups负责限制:它用于限制和记录进程组使用的资源,包括 CPU 时间、内存用量、磁盘 I/O、网络带宽等。这是实现“运行超时”、“内存超限”判定的关键技术。
在实际部署中,Recodex 常与Isolate这样的沙箱工具集成。Isolate 专门为评测系统设计,它通过一个简单的命令行接口,接收要运行的程序、输入、资源限制等参数,然后在隔离的环境中启动进程,并返回运行结果和资源使用情况。评测后端的工作节点会调用 Isolate 来执行每一次学生代码的运行。
注意:沙箱的安全性并非绝对。历史上,一些判题系统也曾出现过“逃逸”漏洞。因此,保持沙箱工具和内核的更新,并遵循最小权限原则(只挂载必要的文件,只赋予必要的系统调用权限)至关重要。在 Recodex 的配置中,你需要仔细审查
isolate的配置参数,例如--dir参数定义哪些目录可被访问,--meta文件定义了资源限制的细节。
2.3 数据流与核心组件协作
理解 Recodex 的数据流,有助于你在部署和调试时快速定位问题。一次典型的作业提交与评测流程如下:
- 提交:学生通过 Web 前端提交代码文件。前端 API 服务接收请求,进行基础验证(如文件类型、大小),然后将作业信息(谁、什么作业、什么文件)存入数据库,并将代码文件上传至专用的文件存储服务(如 S3 或本地文件服务器)。
- 任务分发:API 服务创建一个新的“评测作业”记录,并将其放入消息队列(如 RabbitMQ)。这是一个异步过程,学生提交后立即得到“已接收”的反馈,无需等待评测完成。
- 任务执行:一个或多个“评测工作者”进程在监听消息队列。它们获取到任务后,开始执行核心评测逻辑: a.准备阶段:从文件存储下载学生的代码和该作业对应的评测配置(测试用例、评分规则)。 b.沙箱初始化:为本次运行创建沙箱环境,将必要的文件(代码、输入用例)复制或挂载到沙箱内。 c.编译与运行:根据编程语言,在沙箱内执行编译命令(如
gcc main.c -o main)。编译失败则直接记录结果。编译成功后,针对每一个测试用例,在沙箱内运行可执行文件,并传入对应的输入数据。 d.结果收集:捕获程序的标准输出和标准错误输出。与预期的输出文件进行比对(可以是精确匹配、浮点数容差匹配、正则表达式匹配等)。同时,从沙箱工具(如 Isolate)获取资源使用报告(运行时间、内存峰值)。 e.评分计算:根据预设的评分规则(例如,通过一个测试用例得 10 分,内存超限扣 5 分),计算出该次运行的得分。 - 结果回写:工作者将详细的评测结果(分数、每个用例的对错、输出对比、资源使用)写回数据库,并可能通过 WebSocket 或轮询机制通知前端更新状态。
- 结果展示:学生和教师在前端页面可以看到清晰的评测报告。教师后台还能生成统计图表。
这个流程中,消息队列解耦了提交和评测,使得系统具备良好的弹性和扩展性;文件存储服务独立出来,便于管理和备份海量的学生代码;数据库记录了所有的元数据和结果,是查询和分析的基础。
3. 核心功能模块深度拆解
3.1 多语言支持与评测配置
Recodex 的强大之处在于它对多种编程语言的“开箱即用”支持。这不仅仅是能运行python3 solution.py那么简单。它需要为每种语言定义一整套“生命周期”钩子。
在 Recodex 的配置中,每种语言对应一个“评测配置”。这个配置通常是一个 YAML 或 JSON 文件,定义了以下关键信息:
- 识别器:如何识别学生提交的文件属于该语言(如文件后缀
.py,.java)。 - 编译命令:如果需要编译,命令是什么(如
javac Main.java)。对于解释型语言,此步骤可能为空或是一个语法检查命令。 - 执行命令:如何在沙箱中运行程序(如
python3 solution.py或./a.out)。这里需要注意可执行文件的路径和参数。 - 默认资源限制:该语言通常的 CPU 时间、内存限制(Java 程序通常需要更多内存)。
- 文件过滤器:编译后可能产生哪些中间文件(如
.class,.o文件),这些文件在评测完成后是否需要清理。
例如,一个 C++ 语言的配置可能如下所示(概念示例):
language: "cpp" extensions: [".cpp", ".cc", ".cxx"] compilation: command: "g++ -std=c++17 -O2 -o solution {source_files}" executable: "solution" execution: command: "./solution" limits: time: 2.0 # 秒 memory: 256 # MB extra_time: 0.5 # 编译额外时间教师发布作业时,需要选择或指定一个评测配置。更高级的用法是编写自定义的“评测管道”,例如先运行学生的代码,然后用教师的参考输出进行比对,或者运行特定的测试脚本。
3.2 灵活的评分策略与测试用例管理
评分是教学评价的核心。Recodex 提供了非常灵活的评分机制,远不止“对/错”二分法。
测试用例是评分的基础。每个作业可以关联多个测试用例,每个用例包含:
- 输入数据:一个文本文件。
- 期望输出:一个文本文件,或一个用于验证输出的脚本。
- 权重:该用例在总分中的占比。
- 比较器:如何比较学生输出和期望输出。常见的有:
diff:精确的文本比对。float:容忍浮点数误差(例如,绝对误差或相对误差在 1e-6 以内)。custom:调用自定义的脚本或程序进行比对,适用于输出格式复杂或需要动态判断的情况。
评分策略则定义了如何根据测试用例的结果计算最终分数。最简单的策略是加权求和。但 Recodex 支持更复杂的策略,例如:
- 分组评分:将测试用例分为若干组(如“基础功能组”、“边界条件组”、“性能组”),只有通过组内所有用例,才能获得该组的分数。
- 依赖关系:用例 B 可能依赖于用例 A 的结果(模拟渐进式开发)。
- 惩罚机制:运行超时或内存超限,不仅该用例不得分,还可能从总分中扣除一定分数。
在管理后台,教师可以方便地上传测试用例文件(支持批量上传),并通过拖拽等方式设置权重和分组。系统还支持“隐藏用例”,即对学生不可见的测试用例,用于考察一些边界或恶意输入,防止学生针对公开用例“硬编码”输出。
3.3 代码相似度检测与反抄袭
编程作业抄袭是一个普遍且棘手的问题。Recodex 集成了代码相似度检测功能,通常是基于MOSS(Measure Of Software Similarity)或JPlag这类工具。
其工作流程通常是异步的:
- 在一次作业的所有提交截止后,系统会触发相似度检测任务。
- 评测后端将所有学生的源代码文件收集起来,按照作业和语言进行分类。
- 调用 MOSS 或 JPlag 的接口,上传这些文件。这些工具会进行语法解析、生成指纹、并计算代码片段之间的相似度。
- 工具会生成一个包含相似度矩阵和可疑代码对高亮显示的 HTML 报告。
- Recodex 将报告存储在文件系统中,并在教师后台提供链接。教师可以查看详细的比对结果,从而判断是否存在抄袭行为。
实操心得:相似度检测是一个辅助工具,而非最终判决。高相似度可能源于使用了相同的教学模板、解决了相同的问题(导致算法自然相似),或者是合理的合作(如果作业允许)。因此,教师需要结合报告进行人工审查,并与学生沟通,了解代码相似的真正原因。最好在课程开始时,就明确告知学生关于合作和抄袭的政策。
3.4 管理后台与数据分析
对于教师而言,Recodex 的管理后台是一个强大的指挥中心。除了基本的作业、学生、班级管理外,数据分析功能尤为亮眼。
- 成绩总览:以表格和图表形式展示全班学生的成绩分布(直方图),平均分、中位数、标准差一目了然。可以快速发现本次作业的整体难度。
- 提交统计:查看每位学生的提交次数、首次提交时间、最后一次提交时间。这有助于了解学生的学习投入和 procrastination(拖延)情况。
- 错误聚类:系统可以分析所有失败的评测结果,将相同的错误信息(如“编译错误:未定义的引用”、“运行时错误:除零异常”)进行聚类。教师可以迅速看到学生最常犯的错误类型,从而在课堂上进行集中讲解。
- 积分榜与活跃度:可以设置积分规则(如按时提交加分、挑战题额外加分),形成积分榜,增加学习的游戏化和竞争性。
这些数据驱动的洞察,让教学从“凭感觉”变得“有依据”,使得教师能够进行精准的教学干预。
4. 部署与运维实战指南
4.1 环境准备与依赖安装
部署 Recodex 是一个系统工程,建议在 Linux 服务器(如 Ubuntu 22.04 LTS)上进行。以下是核心依赖:
- Docker 与 Docker Compose:这是官方推荐的部署方式,能极大简化微服务之间的依赖管理和部署流程。安装最新版本的 Docker Engine 和 Docker Compose Plugin。
- 数据库:Recodex 使用 PostgreSQL 作为主数据库。你需要一个 PostgreSQL 实例(版本 12+)。在 Docker 部署中,这通常作为一个容器运行。
- 消息队列:使用 RabbitMQ 作为任务队列。同样,在 Docker 环境中作为容器运行。
- 文件存储:可以选择本地文件系统(对于小规模部署)或兼容 S3 协议的对象存储(如 MinIO,或云服务商的 S3)。对于生产环境,对象存储更利于扩展和备份。
- 沙箱工具:需要在运行评测工作者的主机上安装和配置Isolate。这不是一个 Docker 容器内的工具,因为它需要直接与主机内核交互以创建沙箱。你需要从源码编译安装 Isolate,并确保其二进制文件在 PATH 中,且
isolate命令可以以 root 权限安全地执行(通常通过 setuid 或配置 sudo 规则给特定的工作者用户)。 - 反向代理:使用 Nginx 或 Traefik 作为反向代理,处理 HTTPS、域名路由和负载均衡。
4.2 基于 Docker Compose 的一键部署
官方仓库提供了docker-compose.yml文件,这是最快速的入门方式。部署步骤大致如下:
# 1. 克隆仓库 git clone https://github.com/adryfish/recodex.git cd recodex/deployment/docker # 2. 复制环境变量配置文件并编辑 cp .env.template .env # 使用编辑器(如 vim, nano)修改 .env 文件 # 关键配置项: # - POSTGRES_PASSWORD: 数据库密码 # - SECRET_KEY: Django 应用的密钥(用于加密会话等) # - ALLOWED_HOSTS: 你的服务器域名或IP # - FILE_STORAGE__TYPE: 文件存储类型(`local` 或 `s3`) # - 如果使用 S3,还需配置 S3 的访问密钥、端点等信息。 # 3. 启动所有服务 docker-compose up -d这个命令会拉取并启动包括前端、后端 API、PostgreSQL、RabbitMQ、Redis(缓存)、文件服务器(如果配置为 local)等在内的所有容器。
关键配置解析:
.env文件:这是所有服务的核心配置入口。务必妥善保管,尤其是密码和密钥。- 网络:Docker Compose 会创建一个独立的网络,所有服务通过服务名(如
postgres,rabbitmq,backend)相互通信。 - 数据持久化:查看
docker-compose.yml,确保 PostgreSQL 的数据卷(volumes)映射到了宿主机,这样数据库数据在容器重建后不会丢失。 - 文件存储:如果使用
local类型,文件会存储在某个容器内。生产环境建议映射到宿主机特定目录,或直接配置为s3。
4.3 评测工作者(Worker)的特殊部署
评测工作者是唯一需要直接调用宿主机 Isolate 的服务,因此它不能完全运行在容器内。常见的部署模式是:
- 使用 Docker Compose 启动所有其他服务(API、数据库等)。
- 在宿主机上直接运行工作者进程。工作者进程需要能够访问到 RabbitMQ(消息队列)和文件存储服务。
工作者通常是一个独立的可执行文件或 Python 脚本。你需要从源码构建或下载它,并为其创建配置文件,指明 RabbitMQ 的连接信息、文件存储的访问方式等。
一个简化的启动工作者命令可能如下:
# 假设工作者程序是 recodex-worker ./recodex-worker --config worker-config.yaml在worker-config.yaml中,你需要配置:
broker: url: "amqp://guest:guest@rabbitmq-host:5672//" # RabbitMQ 地址 fileserver: type: "s3" url: "https://your-minio-endpoint" access_key: "..." secret_key: "..." isolate: path: "/usr/bin/isolate" # Isolate 二进制路径 use_cg: true # 是否使用 cgroups注意事项:工作者进程的运行用户权限需要仔细规划。它需要能执行
isolate命令(这通常需要 root 权限或通过 sudo 配置),但同时为了安全,它自身不应以 root 身份运行。一种常见的做法是创建一个专用用户(如recodex-worker),然后通过/etc/sudoers精细配置,允许该用户无需密码以 root 身份运行isolate命令,并限制其参数。这是一个安全关键点,配置错误可能导致严重的安全漏洞。
4.4 日常运维与监控
系统上线后,运维工作主要包括:
- 日志收集:所有服务的日志都应被集中收集(如使用 Docker 的日志驱动发送到 ELK Stack 或 Loki)。重点关注评测后端的错误日志和工作者的执行日志。
- 资源监控:
- 数据库:监控连接数、慢查询。PostgreSQL 数据会随着时间增长,需要定期清理或归档旧的提交记录(如果政策允许)。
- 消息队列:监控 RabbitMQ 的队列长度。如果队列持续积压,说明工作者处理不过来,需要增加工作者实例。
- 工作者主机:监控 CPU、内存、磁盘 I/O。Isolate 运行学生代码是计算密集型操作。
- 文件存储:监控存储空间使用情况。
- 备份策略:
- 数据库:定期使用
pg_dump进行逻辑备份,并测试恢复流程。 - 文件存储:如果使用本地存储,需要定期备份文件目录。如果使用 S3,确保开启了版本控制或跨区域复制。
- 配置文件:将
.env、docker-compose.yml和各种服务配置文件纳入版本控制(如 Git)。
- 数据库:定期使用
- 升级:关注 Recodex 官方仓库的 Releases 和更新日志。升级前,务必在测试环境进行全流程验证。升级步骤通常包括:拉取新镜像、更新配置文件、按顺序重启服务(通常先数据库,再中间件,最后应用)。
5. 高级定制与二次开发
5.1 集成现有用户系统(LDAP/OAuth)
大多数学校或企业已有自己的统一身份认证系统(如 LDAP/Active Directory)。让师生用两套账号密码是不可接受的。Recodex 支持通过插件或配置集成外部认证。
对于LDAP,你需要在 Recodex 的配置文件中(通常是后端 API 的配置)设置 LDAP 服务器的连接参数、基准 DN、用户名字段映射等。当用户登录时,Recodex 会将凭证转发到 LDAP 服务器进行验证,验证通过后,在本地数据库创建或更新对应的用户记录(通常只保存用户名、邮箱等基本信息)。
对于OAuth 2.0(如使用 Google、GitHub 或学校的统一门户登录),Recodex 可以作为 OAuth Client。你需要在校方的身份提供商(IdP)注册一个应用,获取 Client ID 和 Secret,然后在 Recodex 配置中填入。这样,登录按钮会跳转到 IdP 的页面,授权后跳回 Recodex 并完成登录。
集成外部认证后,班级和学生名单的同步可能还需要额外工作。一种常见模式是:定期从学校的课程管理系统(如 Moodle, Canvas)通过 API 同步选课名单到 Recodex,或者允许教师手动在 Recodex 后台导入学生名单(通过学号/邮箱,这些信息应与认证系统一致)。
5.2 编写自定义评测器与比较器
虽然 Recodex 内置了多种比较器,但有时你需要更特殊的评测逻辑。例如:
- 评测一个图形学作业,需要渲染图像并与标准图像进行视觉相似度比较。
- 评测一个数据库作业,需要连接到一个临时数据库,执行学生的 SQL 脚本,然后检查数据库的状态。
- 评测一个机器学习作业,需要运行学生的训练脚本,并在一个保留测试集上计算模型准确率。
这时,你需要编写自定义评测器。Recodex 允许你指定一个可执行文件(如 Shell 脚本、Python 脚本、二进制程序)作为评测管道。这个脚本会接收到相关路径作为参数(学生代码路径、测试用例输入输出路径、工作目录等),你的脚本需要完成编译、运行、比对、打分等一系列操作,最后按照 Recodex 约定的格式(通常是 JSON)将结果输出到标准输出。
例如,一个简单的自定义比较器脚本(Python)框架如下:
#!/usr/bin/env python3 import sys import json import subprocess import os def main(): # Recodex 会传入参数,如学生输出文件路径、参考输出文件路径 student_output_path = sys.argv[1] reference_output_path = sys.argv[2] # 你的比较逻辑 with open(student_output_path, 'r') as f1, open(reference_output_path, 'r') as f2: student_lines = f1.readlines() reference_lines = f2.readlines() # 假设我们进行行数比对 passed = (len(student_lines) == len(reference_lines)) score = 1.0 if passed else 0.0 # 输出 Recodex 期望的 JSON 结果 result = { "score": score, "passed": passed, "message": "Line count matched." if passed else f"Line count mismatch: student {len(student_lines)}, reference {len(reference_lines)}." } print(json.dumps(result)) if __name__ == "__main__": main()然后在作业的评测配置中,将比较器类型设置为custom,并指定此脚本的路径。这为评测几乎任何类型的作业打开了大门。
5.3 性能调优与大规模部署建议
当学生数量达到数千甚至上万时,系统面临的压力是巨大的。以下是一些调优和扩展建议:
- 工作者水平扩展:这是最直接的扩展方式。增加评测工作者实例的数量。确保工作者主机有足够的 CPU 核心(因为 Isolate 运行是 CPU 密集型)和内存。可以使用进程管理工具(如 systemd, supervisord)来管理多个工作者进程,或者使用容器编排平台(如 Kubernetes)来动态调整工作者副本数。
- 优化消息队列:确保 RabbitMQ 有足够的内存和磁盘空间。可以设置多个队列,将不同优先级或不同语言的作业分发到不同队列,由专属的工作者集群处理。
- 数据库优化:
- 为经常查询的字段建立索引,如
(assignment_id, user_id),created_at。 - 考虑对历史评测结果进行分表或归档,保持主表轻量。
- 使用数据库连接池,并合理设置连接数。
- 为经常查询的字段建立索引,如
- 文件存储优化:使用高性能的对象存储(如 AWS S3, 阿里云 OSS)。对于频繁访问的作业附件或测试用例,可以结合 CDN 进行加速。
- 缓存策略:使用 Redis 缓存频繁访问且不常变的数据,如作业配置、用户基本信息、公共公告等。
- 异步与批处理:将耗时的操作异步化。例如,代码相似度检测、生成复杂的统计报表,都可以通过消息队列触发后台任务,完成后通知用户。
- 监控与告警:建立完善的监控体系。当队列积压超过阈值、工作者失败率升高、数据库慢查询增多时,及时发出告警。
6. 常见问题与故障排查实录
在实际部署和运行 Recodex 的过程中,你肯定会遇到各种问题。以下是我总结的一些典型场景和排查思路。
6.1 评测失败:沙箱相关错误
问题现象:学生提交作业后,状态长时间显示“评测中”然后变为“评测失败”,或直接显示“系统错误”。工作者日志中出现Isolate error,Sandbox error等。
排查步骤:
- 检查 Isolate 安装与权限:在工作者主机上,运行
isolate --version确认安装成功。尝试以工作者进程的运行用户身份执行一个简单的沙箱命令,例如isolate --run -- /bin/echo hello。如果提示权限不足,检查/etc/sudoers配置或isolate二进制文件的 setuid 位。 - 检查资源限制:学生的程序可能因为超出内存或时间限制被沙箱杀死。查看具体的错误信息。如果是
Time limit exceeded或Memory limit exceeded,这属于正常的评测结果,系统应能正确处理并扣分。如果是因为限制设置过严导致正常程序也无法运行,则需要调整作业的默认资源限制。 - 检查文件系统访问:学生的代码可能需要读取额外的输入文件。确保在评测配置中,通过
--dir参数将必要的目录(如包含测试用例的目录)以只读方式挂载到了沙箱内。 - 检查系统调用限制:某些程序可能需要特殊的系统调用(如
clone,fork)。Isolate 默认会禁用一些危险的系统调用。如果学生的合法程序因此失败,可能需要在 Isolate 的编译配置或运行参数中调整白名单。此举需非常谨慎,并充分评估安全风险。
6.2 前端无法连接后端 API
问题现象:浏览器打开 Recodex 页面,一直加载,或提示“无法连接到服务器”。浏览器开发者工具的网络选项卡中显示 API 调用返回 5xx 或连接失败。
排查步骤:
- 检查服务状态:运行
docker-compose ps查看所有容器是否都处于Up状态。重点检查backend(API) 和frontend容器。 - 检查容器日志:使用
docker-compose logs backend查看后端 API 的日志。常见的启动错误包括:数据库连接失败(检查.env中的POSTGRES_*配置)、RabbitMQ 连接失败、配置文件语法错误、SECRET_KEY 未设置等。 - 检查网络连通性:确保前端容器能通过网络访问到后端容器。在 Docker Compose 中,它们通常通过服务名(如
http://backend:8000)通信。你可以进入前端容器 (docker-compose exec frontend sh) 并尝试curl http://backend:8000/api/看是否通。 - 检查反向代理配置:如果你使用了 Nginx,检查其配置是否正确地将
/api/等路径代理到了后端服务,将/代理到了前端服务。同时检查防火墙是否开放了相关端口。
6.3 作业提交后长时间处于“排队中”
问题现象:学生提交作业后,状态一直显示“排队中”,不进入评测。
排查步骤:
- 检查 RabbitMQ 队列:这是最可能的原因。访问 RabbitMQ 的管理界面(默认端口 15672),查看队列中是否有积压的消息。如果队列是空的,说明消息可能没有成功发布。
- 检查工作者状态:确认至少有一个评测工作者进程正在运行,并且其日志显示它正在正常地从 RabbitMQ 消费消息。工作者可能因为配置错误、依赖缺失或权限问题而崩溃。
- 检查 API 日志:查看后端 API 日志,确认提交请求被正确处理,并且成功向 RabbitMQ 发布了消息。查找是否有相关的错误信息。
- 检查作业配置:确保该作业关联的评测配置是有效的,并且所需的测试用例文件已正确上传到文件存储中。如果配置有误,工作者可能在获取任务后因无法初始化而失败。
6.4 数据库性能瓶颈
问题现象:系统响应变慢,特别是在查询成绩列表或提交历史时。数据库监控显示 CPU 或 IO 使用率高。
排查步骤:
- 分析慢查询:在 PostgreSQL 中,启用
log_min_duration_statement记录慢查询日志。分析是哪些 SQL 语句执行缓慢。 - 添加索引:最常见的优化手段。对于
WHERE,ORDER BY,JOIN子句中频繁使用的字段,考虑添加索引。例如,在submissions表上对(assignment_id, user_id, created_at)创建复合索引,可以极大加速按作业和用户查询提交历史的操作。 - 优化查询:有时前端或 API 的查询逻辑可能不够优化,比如一次性拉取过多数据(如所有学生的所有提交)。可以考虑实现分页查询,或者只拉取必要字段。
- 考虑读写分离与归档:对于超大规模部署,可以考虑 PostgreSQL 的主从复制,将报表类读操作导向从库。同时,制定数据保留策略,将超过一定时间的旧提交记录迁移到归档表或冷存储中,减轻主表的压力。
部署和运维这样一个复杂的系统,挑战与成就感并存。最关键的是建立完善的监控和日志体系,这样当问题出现时,你才能像侦探一样,顺着线索快速找到根因。Recodex 作为一个成熟的开源项目,其社区和 issue 列表也是宝贵的资源,很多你遇到的问题,可能已经有人遇到并解决了。
