ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AIGC赋能在线编程评测系统:架构设计与实践

AIGC赋能在线编程评测系统:架构设计与实践 简介基于人工智能生成内容AIGC技术的在线编程题目评测系统完整工程源码面向编程教育平台开发者、计算机专业学生及在线评测系统研究人员。系统整合了题目自动生成、代码智能评测、实时反馈、个性化学习路径推荐、用户认证授权、数据缓存与异步消息处理等核心模块可支撑从基础语法到复杂算法的题目练习与评测场景。资源共61个文件压缩包约81KB以46个Java源文件为主体辅以XML、YML及properties等配置文件并包含SQL脚本、Maven配置、README说明与附赠素材整体覆盖后端业务实现、测试用例、数据库初始化及项目构建配置。当前已有53人学习下载适合希望快速理解AIGC教学平台整体架构、参考工程化实现细节的读者借助代码结构与文档说明可梳理出题目生成、评测反馈、路径推荐的完整业务流程。1. 在线编程评测系统为什么这次把 AIGC 放进题库生产线校园 OJ 里最磨人的不是写题而是提交之后那十几秒的等待以及题库里翻来覆去那几百道老题。基于 AIGC 的在线编程题目评测系统就是把“出题”和“判题”这两件最重的事都自动化大模型按知识点生成题目与测试用例评测服务在沙箱里跑代码、比对输出、回传实时反馈再叠加学习路径推荐、用户认证授权、数据缓存和异步消息处理构成一个能直接部署的在线编程练习闭环。它适合正在做在线教育、OJ 平台或编程训练营产品的团队也适合想把题库运营成本降下来的教学管理者。2. 整体架构与技术选型把六个模块串成一条数据流水线2.1 一条完整的请求链路登录、出题、提交、评测、回传先别急着选框架把这条链路走一遍你就知道哪些地方必须用异步、哪些地方必须做缓存。第一个环节是用户认证授权。用户在 Web 端登录后端签出 JWT前端把 token 存下来后续每次请求头里带上。这里就一个要求认证服务必须无状态不然分布式部署的时候 session 同步会变成噩梦。角色上一般分学生、教师、管理员三种教师能审核题目、看统计数据管理员管用户和沙箱节点。RBAC基于角色的访问控制在这个规模下够用不需要上图谱权限那样重的模型。第二个环节是 AIGC 出题。学生或教师发起“生成一道题”的请求先把生成任务扔给消息队列立刻返回“生成中”。大模型推理耗时可能十几秒甚至半分钟如果 HTTP 请求同步等前端早就超时了。生成服务消费消息调用大模型 API拿到题目描述、输入输出格式、测试用例然后做校验校验通过才进题库。第三个环节是提交与评测。这是整个系统最重的流量入口。学生提交代码后端不做任何判题动作只把提交记录落库、状态置为 PENDING然后往评测队列里丢一条消息立即返回“已提交”。评测节点从队列里取消息拉起沙箱编译、运行、比输出再把结果写回通过 WebSocket 推给前端。用户看到的是“评测中 → Accepted/WA”背后的异步链路已经把瞬时流量摊平了。第四个环节是学习路径推荐。评测结果产生后系统更新用户的知识点掌握度推荐下一个该练的题。这个模块不要求实时晚一分钟都无所谓所以可以用异步消费者慢慢算。整条链路走完你会发现一个规律凡是耗时不确定的操作生成题目、评测代码、更新推荐全部放异步凡是读多写少的数据题目详情、用户信息、排行榜全部走缓存。这就是后面技术选型的依据。2.2 技术选型优先级隔离、削峰、读缓存为什么排在最前面在线编程评测和普通业务系统有个本质区别你要执行一段来自用户的、未知的、可能存在恶意行为的代码。因此选型优先级里沙箱隔离比数据库选型更关键。其次是并发削峰因为评测计算耗时瞬间大量提交会直接把评测节点打挂。最后才是常规的缓存和 ORM 选择。下面这套组合是社区里跑得最多、踩坑资料也最多的方案。不是唯一解但照着搭不会走偏。模块常见选型选型理由Web 框架Spring Boot / FastAPI生态成熟、连接池/线程池管理完善方便对接 Redis 和 MQ业务数据库PostgreSQL / MySQL题目、提交记录、用户表都是强事务数据缓存Redis题目详情、排行榜、用户会话这类读多写少的数据消息队列RabbitMQ / Kafka提交评测任务、生成题目任务的削峰与解耦评测沙箱Docker 资源限制隔离用户代码API 成熟社区案例多实时推送WebSocket评测状态变化实时推到浏览器认证JWT RBAC无状态便于水平扩展选型逻辑再强调一下框架层面用你团队最熟的那个就行真正影响命运的选型是消息队列和沙箱。消息队列决定你能扛多大瞬时提交量沙箱决定恶意代码能不能搞挂宿主机。缓存更像是提速器等并发真的上来了再调也不迟但架构上要留好位置。部署上我一般会用一个 docker-compose 先把基础设施串起来。下面这个编排文件是常见的最小集合后端服务和评测节点先用镜像占位version: 3.8 services: postgres: image: postgres:15 environment: POSTGRES_DB: oj_platform POSTGRES_USER: oj_user POSTGRES_PASSWORD: oj_password ports: - 5432:5432 volumes: - pg_data:/var/lib/postgresql/data redis: image: redis:7 ports: - 6379:6379 command: [redis-server, --appendonly, yes] rabbitmq: image: rabbitmq:3-management ports: - 5672:5672 - 15672:15672 environment: RABBITMQ_DEFAULT_USER: oj_mq RABBITMQ_DEFAULT_PASS: oj_mq_password judge-node: image: judge-image:latest depends_on: - rabbitmq - redis environment: RABBITMQ_HOST: rabbitmq REDIS_HOST: redis deploy: replicas: 2 volumes: pg_data:参数说明postgres 挂载了命名卷数据不会因为容器重启丢失redis 开了 appendonly 持久化防止缓存节点重启后热数据全丢rabbitmq 暴露了 15672 管理端口方便看队列积压judge-node 的 replicas 设成 2这是最朴素的评测节点扩容方式。生产环境不要把端口映射暴露到公网内网访问即可。2.3 核心表结构与任务状态机不建模清楚后面全是隐患评测系统的核心表不多但状态机必须提前定义清楚否则后面排查超时、断线、重复消费都会很痛苦。提交记录表是核心中的核心我常这样设计CREATE TABLE submission ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL, problem_id BIGINT NOT NULL, code TEXT NOT NULL, language VARCHAR(20) NOT NULL, status VARCHAR(20) NOT NULL DEFAULT PENDING, judge_score INT, used_time_ms INT, used_memory_kb INT, error_message TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE INDEX idx_submission_user_problem ON submission(user_id, problem_id); CREATE INDEX idx_submission_status ON submission(status) WHERE status IN (PENDING, JUDGING);status 字段的流转是评测系统里最容易翻车的地方PENDING 表示消息已投递、还没被评测节点消费JUDGING 表示沙箱正在跑AC、WA、TLE、MLE、RE、CE 是终态还有一个特殊状态 JUDGE_ERR表示评测节点本身出问题了不是用户代码的问题。所有中间状态都要带 updated_at 时间戳超时排查就靠它。为什么要在状态机上花这么多精力因为生产环境里你会遇到消息重复消费、评测节点宕机、消费者重启等意外。状态机清晰至少你能回答三个问题这条提交卡在哪一步卡了多久是用户代码问题还是基础设施问题。就算是 PENDING 超过一分钟的积压也能通过查队列长度快速定位。3. 代码自动评测引擎沙箱隔离与判定逻辑的落地细节3.1 沙箱选型为什么不能直接 subprocess 跑用户代码见过不少内部试验项目一开始图省事直接在服务端 subprocess 调用系统编译器跑用户代码结果上线没几天就出事故。最轻的是用户写个 while True 把 CPU 打满拖垮整个服务最重的是遇到会读环境变量、扫描内网端口、把宿主文件删了的代码。评测系统的沙箱不是功能需求是底线需求。常见的隔离方案按强度分三档。第一档是纯软件的语言虚拟机限制比如 Python 的 resource 模块限制内存和 CPU 时间只能挡住初学者挡不住恶意代码第二档是 nsjail 这类基于系统调用的隔离沙箱性能损耗低但配置比较复杂对多语言支持需要自己适配第三档是 Docker 容器加资源配额这也是最主流的做法——每个评测任务启动一个一次性容器限制 CPU、内存、进程数、网络权限跑完销毁。三者的对比可以这样看方案隔离强度性能损耗配置复杂度适用场景resource 模块低极低极低原型验证nsjail中高低高对性能极敏感的大规模 OJDocker 容器高中中绝大多数生产平台我的建议是新项目直接上 Docker别在 nsjail 上折腾。等你的评测节点需要追求极致吞吐、Docker 启动开销成为瓶颈时再考虑用 nsjail。Docker 不是没有坑它的坑在资源限制参数需要显式声明不声明就默认共享宿主机资源这部分下面避坑章节细说。3.2 判定逻辑编译、运行、限时、比对一条龙附评测核心代码评测节点是消费者从队列里拿到一条任务后处理逻辑固定五步拉代码、准备沙箱、编译、跑测试用例、比对输出。下面这段 Python 伪代码是核心流程去掉业务包装后就是这个骨架评测节点核心流程消费消息 - 沙箱执行 - 写回结果 import docker import redis import pika import uuid MAX_CPU_TIME 2 # 单测试用例最大CPU时间(秒) MAX_MEMORY 256 * 1024 # 单测试用例最大内存(KB) IMAGE_NAME oj-python-runner:latest def judge_task(ch, method, properties, body): task json.loads(body) submission_id task[submission_id] code_path task[code_path] language task[language] test_cases task[test_cases] # [{input, expected}, ...] client docker.from_env() container_name fjudge-{submission_id}-{uuid.uuid4().hex[:8]} try: # 1. 编译阶段把用户代码挂载进容器内编译失败的直接判 CE result client.containers.run( IMAGE_NAME, commandfpython3 -m py_compile /workspace/main.py, volumes{code_path: {bind: /workspace/main.py, mode: ro}}, mem_limitf{MAX_MEMORY}k, nano_cpus1_000_000_000, # 1 个 CPU 核心 network_disabledTrue, pids_limit64, detachTrue, removeTrue, ) compile_result result.wait(timeout10) if compile_result[StatusCode] ! 0: write_result(submission_id, CE, 0, 0, compile error) return # 2. 跑每个测试用例逐个限制时间和内存 for i, case in enumerate(test_cases): run_result client.containers.run( IMAGE_NAME, commandfpython3 /workspace/main.py, stdin_openTrue, volumes{code_path: {bind: /workspace/main.py, mode: ro}}, mem_limitf{MAX_MEMORY}k, nano_cpus1_000_000_000, network_disabledTrue, pids_limit64, detachTrue, removeTrue, ) # 写入输入并等待结果 run_result.exec_run(cmdsh -c cat /workspace/input.txt, stdinTrue, datacase[input]) wait_res run_result.wait(timeoutMAX_CPU_TIME 1) logs run_result.logs(stdoutTrue, stderrTrue).decode() if run_result.attrs[State][OOMKilled]: write_result(submission_id, MLE, 0, MAX_MEMORY, memory limit exceeded) return if wait_res[StatusCode] ! 0: write_result(submission_id, RE, 0, 0, logs[-500:]) return # 3. 输出比对先归一化再比较 actual normalize_output(logs) expected normalize_output(case[expected]) if actual ! expected: write_result(submission_id, WA, 0, 0, fcase {i} failed) return write_result(submission_id, AC, used_time_ms, used_memory_kb, ) except docker.errors.ContainerError as e: write_result(submission_id, JUDGE_ERR, 0, 0, str(e)) finally: try: client.containers.get(container_name).remove(forceTrue) except docker.errors.NotFound: pass参数说明mem_limit 用 256MB 上限nano_cpus 是 1 个核心network_disabledTrue 断网pids_limit 限制进程数防止 fork 炸弹。这里的逻辑说明一下编译和运行分开编译失败直接 CE运行阶段每个测试用例独立计时超出 wait(timeout) 会抛异常映射成 TLE 更合理。输出比对先走 normalize_output 去掉行尾空白和统一换行符避免学生代码和标准输出之间因为肉眼不可见的差异误判。每个用例失败立即返回 WA不需要跑完所有用例。实际生产里test_cases 不是跟消息一起传的而是题目表里存一份、评测节点从 Redis 或数据库取。上面代码为了展示流程做了简化。另外Docker API 每次 containers.run 都要拉镜像或启动容器开销不小所以生产系统会做一个“预启动容器池”需要时直接复用这个在最后一章讲。3.3 实时反馈用 WebSocket 替代轮询把评测状态推到浏览器评测结果写回数据库后前端怎么感知状态变化老办法是轮询每 2 秒发一次请求查状态简单粗暴但用户开着页面不动也要持续消耗请求资源。评测场景下一次评测通常 3 到 10 秒轮询的浪费非常明显。换 WebSocket 之后评测节点每次更新状态就 push 一条消息到前端。链路是评测节点写库 → Redis 发布订阅或消息队列广播 → WebSocket 服务端推给对应连接。按用户 ID 维护连接映射就行。这里的关键参数是心跳间隔和断线重连心跳间隔建议 30 秒前端检测到连接断开要自动重连并补拉一次当前状态防止断线期间状态更新丢失。WebSocket 不是没坑反向代理需要开启长连接支持Nginx 默认的 proxy_read_timeout 60 秒会导致频繁断开Docker 部署时要给 WebSocket 服务预留连接数上限。这些在避坑章节展开。能用 WebSocket 就尽量别用 Server-Sent EventsSSE 只能单向推评测场景还需要客户端主动上报“停止评测”之类的指令双向通道更省事。4. AIGC 题目生成Prompt 模板、质量校验与学习路径推荐4.1 Prompt 模板让大模型稳定生成“可运行”题目的关键参数AIGC 在这个系统里承担的第一件事是出题。很多人以为出题就是“让 AI 写一道题”实际跑起来会发现生成出来的题目描述像模像样但测试用例对不上边界条件描述模糊甚至样例输出都是错的。要让大模型稳定产出可用题目关键在于把约束写进 Prompt而不是靠它自由发挥。下面是一个经过反复调校的 Prompt 模板核心是强制结构化输出并让模型同时生成题解代码prompt f 你是一名算法竞赛出题人。请按照以下要求生成一道编程题并以JSON格式返回。 要求 1. 知识点范围{knowledge_point} 2. 难度等级{difficulty}easy / medium / hard 3. 编程语言不限题解用Python 3编写 4. 题目描述需包含完整的输入输出格式说明和样例 5. 边界条件必须明确数据范围、是否可能为空输入 6. 生成3组测试用例覆盖正常场景、边界场景、极端场景 7. 必须同时生成一份通过所有测试用例的题解代码 8. 输出格式严格如下 {{ title: 题目名称, description: 题目描述, input_format: 输入格式, output_format: 输出格式, samples: [{{input: ..., output: ...}}], test_cases: [ {{input: ..., expected: ...}} ], solution_code: 通过测试用例的题解代码, difficulty: {difficulty}, knowledge_points: [{knowledge_point}] }} 请只输出JSON不要输出任何解释文字。 调用大模型 API 时的参数比 Prompt 本身还关键。temperature 设为 0.2太低会变得机械太高会开始编造输入输出格式max_tokens 给 2000某些模型默认值太短导致 JSON 被截断截断了直接解析失败。如果平台支持 JSON mode一定要开输出结构稳定性和解析成功率是两个量级。响应拿到了先用 json.loads 解析失败就丢弃重试一次不要做字符串正则提取那个路子维护成本太高。4.2 质量校验四步生成内容不能直接入库大模型的输出直接入库是新手最容易踩的坑。生成内容在“看起来合理”和“真的能跑”之间隔着一条鸿沟。每一道生成的题目在正式进入题库之前至少要过四道校验格式校验、题解验证、测试用例验证、查重。格式校验最简单就是检查 JSON 字段齐全、类型正确。题解验证是把模型生成的 solution_code 放进沙箱跑一遍——能通过编译且能运行就说明题目描述至少不会自相矛盾。测试用例验证更严格用题解代码跑每一组 test_case期望输出必须和实际输出一致不一致说明测试用例本身有错要重新生成。查重是防止模型“复述”题库里已有的题逻辑上类比论文查重——新题和已有题目文本相似度超过阈值就丢弃。下面是一个校验脚本的骨架放在生成服务里作为一道题入库前的最后关卡题目生成后的自动化校验流程 import json import subprocess import sys def validate_generated_problem(problem_json: dict) - bool: # 1. 格式校验必需字段是否齐全 required [title, description, input_format, output_format, samples, test_cases, solution_code] if not all(k in problem_json for k in required): return False # 2. 题解验证把模型生成的题解代码跑一遍确保能运行 solution problem_json[solution_code] # 这里复用评测沙箱的接口把solution当作提交代码执行 run_result execute_in_sandbox(solution, input_data) if run_result[status] ! AC: return False # 3. 测试用例验证用题解跑每组用例输出必须一致 for case in problem_json[test_cases]: case_result execute_in_sandbox(solution, input_datacase[input]) if case_result[stdout].strip() ! case[expected].strip(): return False # 4. 查重和题库里已有题目做文本相似度比较 if compute_similarity(problem_json[title], problem_json[description]) 0.8: return False return True这里有个细节值得注意第 2 步的输入数据给空字符串是为了验证代码本身不会一运行就崩真正的题解正确性要到第 3 步才验证。查重阈值 0.8 是经验值太高会放过改头换面的重复题太低会误杀正常新题。上面代码里的 execute_in_sandbox 和 compute_similarity 都是占位函数实际接入的时候分别指向沙箱服务和向量库检索。4.3 学习路径推荐先用规则跑起来再谈强化学习题目生成之后系统还要回答用户“接下来练什么”。这个模块经常被过度设计——团队一上来就想用强化学习做自适应学习路径结果数据量不够冷启动问题解决不了推荐结果也没法解释。最稳的做法是先把规则推荐跑起来。核心是三张数据知识点库含前置关系、题目-知识点映射表、用户的提交历史。推荐逻辑基于两个简单原则用户薄弱的知识点优先出题前置知识点没掌握前不推荐依赖它的进阶题。下面是一个规则推荐的 SQL 思路可以直接落地-- 找出用户最近20次提交中通过率最低的知识点 SELECT kp.id, kp.name, AVG(CASE WHEN s.status AC THEN 1.0 ELSE 0.0 END) AS pass_rate FROM submission s JOIN problem p ON s.problem_id p.id JOIN problem_knowledge pk ON p.id pk.problem_id JOIN knowledge_point kp ON pk.knowledge_id kp.id WHERE s.user_id :current_user AND s.created_at now() - interval 30 days GROUP BY kp.id, kp.name ORDER BY pass_rate ASC LIMIT 1;拿到这个薄弱知识点后再从题目表里挑选一道该知识点覆盖、通过率在 40% 到 70% 之间太难打击信心太简单没提升、且用户最近没刷过的题作为推荐结果。这个 SQL 看起来简单但它把“学什么”的决策权交给了历史数据而不是人的拍脑袋。跑一段时间后可以评估这个规则的点击率和完成率用 AB 实验验证优化方向等积累了足够行为数据再引入模型化推荐。学习路径推荐的另一个方向是按“知识点图谱”做路径规划先确定起点知识点用图的广度优先搜索生成一条从基础到进阶的路径推荐时按路径推进。这个方案比单纯看通过率更稳定也不依赖大量训练数据适合作为规则层的第二版升级。5. 避坑名单缓存穿透、消息堆积与 LLM 幻觉的典型翻车现场5.1 生成与评测的坑LLM 幻觉、容器失控、输出比对误判坑1大模型写错了测试用例的预期输出用户代码被冤枉判 WA现象学生提交了一段手算和逻辑都正确的代码评测结果却是 WA。多次反馈后发现同一个样例输出模型给的期望值本身就少了一位小数。原因模型生成题目描述时样例输出是靠模型“推算”出来的复杂的数值计算模型会一本正经地给出错误答案。数据集里 LLM 幻觉导致的错题比想象中普遍甚至能到百分之几的比例。解决Prompt 里强制要求模型同时生成题解代码并且在校验阶段先把题解代码跑一遍用实际执行结果作为预期输出而不是直接用模型生成的文本当标准答案。这道工序不能省——前两周图省事跳过它课代表就提过一次某道 DP 题的用例答案是错的。坑2沙箱容器不设资源上限一个死循环拖垮整个评测节点现象某天线上评测大面积超时排查发现一台评测节点上有一个容器 CPU 占用持续 100%这节点的其他评测任务全部变慢。原因containers.run 没有传 mem_limit 和 nano_cpus容器默认共享宿主机所有资源。容器之间没有隔离边界一个写 while True 的用户代码就能把同节点拖垮。解决所有评测容器的 CPU、内存、进程数限制在编排层强制默认不仅是代码里加参数——在 Docker daemon 配置里设置默认资源配额或者用 docker-compose 的 deploy.resources 限制每个评测节点可调度的总资源。任何评测参数都不能依赖调用方自觉传参。坑3输出比对“只看严格相等”肉眼不可见的空格坑了所有学生现象学生代码输出换行符是 CRLF标准答案是 LF评测结果全是 WA。还有一道题判题标准是忽略空白但代码里实现成了普通精确匹配边界错误全漏了。原因评测比对策略分两种——精确匹配和归一化匹配。不同题型有不同需求字符串匹配类题目要求严格数学计算类题目通常容忍行尾空白和换行差异。一套策略套全部题目必然出问题。解决判题配置增加匹配模式字段normalize 模式统一把换行归一化并去除行尾空白exact 模式保持不变。默认用 normalize字符串类题目手动开 exact。比对前先看匹配模式别一把梭。5.2 缓存与消息队的坑穿透、积压、断线重连坑4Redis 缓存穿透不存在的题目 id 被刷爆数据库现象题库里没有的题目 ID 被持续请求Redis 全部未命中请求直接打到 PostgreSQL数据库连接数被打满正常题目查询也变慢。原因缓存策略只对“存在”的数据做缓存不存在的请求每次穿透。尤其是“生成题目”功能上线后测试同学拿随机 ID 去刷接口或者有人恶意遍历接口。解决对不存在的数据也做空值缓存key 存在value 为 nullTTL 设 60 秒并在缓存前加一层布隆过滤器用题目 ID 集合过滤大概率不存在的 key。这两层加上后穿透请求到不了数据库。另一个附加手段是查询接口做简单的限流按 IP 和用户 ID 双维度。坑5消息队列消息积压评测结果延迟到分钟级现象集中刷题活动一开始用户提交后状态栏卡在“评测中”超过一分钟。看 RabbitMQ 管理后台一条队列堆积了几万条消息。原因消息生产速度远大于消费速度评测节点数量不足单条评测任务要跑多个测试用例执行时间长消费者没有设置合理的 prefetch消息分配不均匀部分节点空转。解决评测队列按题目难度或语言拆分为多个队列消费者动态扩容设置 basic_qos 的 prefetch_count 为 1让节点处理完一条再取下一条另外加上死信队列和重试机制消费失败的消息不丢失。消息堆积的监控阈值要提前配好积压超过 500 条就报警——凡是等用户反馈才发现积压的都已经晚了。坑6WebSocket 连接被代理断开前端一直转“评测中”现象代码提交后前端收不到结果推送页面一直转圈刷新页面后状态立刻变成 AC。原因反向代理的 proxy_read_timeout 默认 60 秒评测任务超过 60 秒或者长时间没有心跳包代理主动断开连接。评测节点推送状态时找不到这条连接状态更新丢失。解决WebSocket 服务端每 30 秒发一次心跳反向代理的 proxy_read_timeout 拉到 3600 秒前端连接断开后自动重连重连成功后主动拉一次当前评测状态做校准不能只依赖推送。这三层缺一不可。6. 压测与缓存预热从 100 并发到 1000 并发的调参记录系统功能完整以后最后一道工序是压测。在线编程评测系统的瓶颈不像普通 Web 应用在数据库而在排队链路和沙箱执行。我习惯直接用提交接口做压测而不是只压一个健康检查接口。# 用 hey 对提交接口做 20 秒、300 并发压测 hey -z 20s -c 300 -m POST \ -H Authorization: Bearer $TOKEN \ -d {problem_id: 101, language: python3, code: print(1)} \ https://api.example.com/api/submission压测结果怎么看先看两个指标——接口本身的 P95 响应时间应低于 500ms以及评测队列积压数量压测结束后是否归零。如果 P95 飙升到几秒说明提交链路里存在同步阻塞点常见的是 SQL 写库太慢或 Redis 操作没走连接池。如果积压数持续增长不归零说明评测节点消费速度跟不上先加节点同时检查沙箱启动耗时——频繁创建容器是最常见的消费瓶颈。解决方式是用容器预启动池评测节点启动时预先拉起 5 个空闲沙箱容器来了任务直接复用跑完清理重置比每次 containers.run 快一个数量级。缓存预热也在这个阶段做把题库高频题目详情、热门排行榜提前刷进 Redis避免压测流量直接打到数据库。这个压测流程我每次发版前都跑一遍跑完顺带把评测节点的 CPU 和内存监控也开上。有了这轮压测数据遇到线上反馈再判断“是代码问题还是资源问题”就有依据了。在线编程评测系统这个方向值得做但它的工作量七成在评测链路的稳定性和边界处理上题目生成反而是最省力的部分。把资源限制和消息链路的参数调教好系统就扛得住真实用户了。希望帮到你。本文还有配套的精品资源点击获取
返回列表