在线判题系统的并发提交处理:消息队列解耦与异步回调设计
在线判题系统的并发提交处理消息队列解耦与异步回调设计一、深度引言与场景痛点当 100 个人同时点提交系统发生了什么V1 版本的判题系统用的是最简单的同步模式用户点提交 → HTTP 请求进入 → 编译代码 → 运行测试用例 → 返回结果 → 用户看到结果。这个流程在单人使用时完美运行响应速度也很快。直到有一次内部测试10 个同事同时提交了不同的题目。崩溃发生了Tomcat 的请求线程池被打满新的请求在排队旧的请求因为判题时间过长把线程一直占着。最终整个服务不可用连健康检查接口都 503 了。问题的根源很简单判题是一个长耗时、CPU 密集型的操作但它被绑定在了短生命周期的 HTTP 请求线程上。HTTP 请求线程的职责是接收请求、返回响应它不应该被一个可能要跑 5 秒的判题过程阻塞。二、底层机制与原理深度剖析异步解耦的核心思想把提交判题和执行判题拆开。这套架构的关键收益API 服务不再阻塞提交接口的响应时间从秒级降到毫秒级判题 Worker 独立扩缩可以通过增加 Worker 数量来提升并发处理能力故障隔离判题 Worker 崩溃不会影响 API 服务的可用性削峰填谷消息队列天然具备缓冲能力应对瞬时提交高峰三、生产级代码实现与最佳实践消息结构定义// 判题任务消息体 —— 包含判题所需的所有信息Worker 可独立处理 Data AllArgsConstructor NoArgsConstructor public class JudgeTaskMessage implements Serializable { private String submissionId; // 提交记录 ID private String problemId; // 题目 ID private String code; // 用户代码 private String language; // 编程语言 private Long submitTime; // 提交时间戳 // 最大重试次数 —— 防止逻辑错误导致无限重试 private int maxRetryCount 3; private int currentRetryCount 0; }提交接口实现// 提交接口 —— 只负责接收和记录不执行判题 RestController RequestMapping(/api/submission) public class SubmissionController { private final SubmissionService submissionService; private final RabbitTemplate rabbitTemplate; PostMapping(/submit) public ApiResponseSubmitResponse submit(RequestBody SubmitRequest request) { // 1. 创建提交记录状态设为 PENDING // 即使判题失败记录也已经持久化用户可以追溯 Submission submission submissionService.createPendingSubmission( request.getProblemId(), request.getCode(), request.getLanguage() ); // 2. 构造消息并发送到队列 // 使用 convertAndSend 确保消息序列化后进入队列 JudgeTaskMessage message new JudgeTaskMessage( submission.getId(), request.getProblemId(), request.getCode(), request.getLanguage(), System.currentTimeMillis() ); // exchange: judge.exchange // routingKey: judge.task.{language} // 按语言路由可以让不同语言的 Worker 分别消费 String routingKey judge.task. request.getLanguage().toLowerCase(); rabbitTemplate.convertAndSend(judge.exchange, routingKey, message); // 3. 立即返回 submissionId用户用它轮询结果 return ApiResponse.success(new SubmitResponse(submission.getId())); } }判题 Worker 实现// 判题 Worker —— 独立消费消息专注执行判题逻辑 Component Slf4j public class JudgeWorker { private final SubmissionRepository submissionRepository; private final JudgeService judgeService; // 并发消费者数量 —— 通过配置中心动态调整 // concurrency: 消费者线程数对应同时判题的并发数 RabbitListener( queues judge.queue.java, concurrency 3-10 // 最少 3 个最多 10 个消费者 ) public void handleJudgeTask(JudgeTaskMessage message) { log.info(开始判题: submissionId{}, language{}, message.getSubmissionId(), message.getLanguage()); try { // 1. 更新状态为 RUNNING用户端可以看到判题中 submissionRepository.updateStatus( message.getSubmissionId(), SubmissionStatus.RUNNING ); // 2. 执行实际判题逻辑 JudgeResult result judgeService.judge( message.getProblemId(), message.getCode(), message.getLanguage() ); // 3. 更新最终结果 submissionRepository.updateResult( message.getSubmissionId(), result.getStatus(), result.getDetails() ); log.info(判题完成: submissionId{}, result{}, message.getSubmissionId(), result.getStatus()); } catch (Exception e) { log.error(判题异常: submissionId{}, message.getSubmissionId(), e); handleJudgeFailure(message, e); } } private void handleJudgeFailure(JudgeTaskMessage message, Exception e) { int retryCount message.getCurrentRetryCount() 1; if (retryCount message.getMaxRetryCount()) { // 重试机制更新重试计数重新投递到延迟队列 // 延迟 30 秒后重试给系统恢复的时间窗口 message.setCurrentRetryCount(retryCount); rabbitTemplate.convertAndSend( judge.exchange, judge.retry, message, msg - { // 设置消息的 TTL 实现延迟投递 msg.getMessageProperties().setExpiration(30000); return msg; } ); } else { // 超过最大重试次数标记为系统错误 submissionRepository.updateStatus( message.getSubmissionId(), SubmissionStatus.SYSTEM_ERROR ); log.error(判题重试耗尽: submissionId{}, retryCount{}, message.getSubmissionId(), retryCount); } } }死信队列处理// 死信队列配置 —— 处理多次重试后仍然失败的消息 Configuration public class DeadLetterConfig { // 死信队列接收重试耗尽的消息避免消息丢失 Bean public Queue deadLetterQueue() { return QueueBuilder.durable(judge.dlq).build(); } Bean public Binding deadLetterBinding() { return BindingBuilder .bind(deadLetterQueue()) .to(deadLetterExchange()) .with(judge.dlq); } // 定时任务每天凌晨检查死信队列人工介入处理 Scheduled(cron 0 0 2 * * ?) public void processDeadLetters() { // 从死信队列中拉取消息记录到告警表 // 这些是自动重试也无法恢复的异常需要人工排查 ListJudgeTaskMessage deadMessages fetchDeadMessages(); if (!deadMessages.isEmpty()) { log.warn(死信队列中有 {} 条未处理消息请尽快排查, deadMessages.size()); alertService.sendAlert(判题死信队列堆积, deadMessages.size()); } } }四、边界分析与架构权衡消息丢失问题RabbitMQ 的消息持久化 手动确认Manual ACK可以最大程度防止消息丢失但以下情况仍需要额外处理Worker 在处理中崩溃消息会重新入队如果开启了 ACK由下一个 Worker 继续处理RabbitMQ 本身宕机磁盘上的持久化消息可以恢复但内存中的瞬态消息会丢失网络分区使用镜像队列Mirrored Queue或 Quorum Queue 提高可用性补偿方案在 API 层增加定时扫描任务检查超过 N 分钟仍为 PENDING 状态的提交记录主动补发判题消息。幂等性保证同一个提交可能被多次投递网络重试、Worker 重连等。Worker 端必须保证判题的幂等性// 幂等性检查 —— 提交 ID 作为幂等键 if (submissionRepository.existsById(message.getSubmissionId())) { Submission existing submissionRepository.findById(message.getSubmissionId()); if (existing.getStatus() ! SubmissionStatus.PENDING) { log.info(跳过重复判题: submissionId{}, 当前状态{}, message.getSubmissionId(), existing.getStatus()); return; // 已经处理过了直接返回 } }同步 vs 异步的选择时机系统阶段推荐方案原因MVP 10 用户同步判题实现简单快速验证内测 100 用户异步 单 Worker解耦接口预留扩展空间正式运营100 用户异步 多 Worker MQ高并发下的唯一选择五、总结将判题从同步改为异步是这个系统架构变化最大的一次升级。核心经验有几点不要把长耗时操作绑定在 HTTP 线程上——它们是稀缺资源消息队列的解耦作用远大于削峰——它让你可以独立演进 API 层和判题层幂等性是异步系统的必修课——永远假设消息可能被投递多次死信队列是最后的安全网——不要让它悄无声息地丢消息对于实习生来说把一个同步服务拆成异步架构是理解分布式系统设计最好的入门项目。因为你能亲手感受到解耦之后的系统调试难度也在同步上升。

相关新闻

心率异常检测端侧推理引擎设计:PPG 信号预处理与 TCN 时序模型在 nRF52 上的部署方案

心率异常检测端侧推理引擎设计:PPG 信号预处理与 TCN 时序模型在 nRF52 上的部署方案

心率异常检测端侧推理引擎设计:PPG 信号预处理与 TCN 时序模型在 nRF52 上的部署方案 一、深度引言 可穿戴健康监测设备的核心价值在于对生理信号的实时、准确分析。PPG(光电容积描记法)传感器以极低功耗采集心率波形,但其原始信号…

2026/7/22 12:52:04阅读更多 →
大模型技术入门:从Python基础到LangChain实战

大模型技术入门:从Python基础到LangChain实战

1. 大模型技术入门指南:从零开始的系统学习路径 作为一名经历过从入门到实战全过程的AI开发者,我深知新手在学习大模型技术时面临的困惑。市面上资料繁杂,工具框架层出不穷,很容易陷入"学了很多却不会用"的困境。这篇指…

2026/7/22 12:52:04阅读更多 →
黄金用量说明查询API聚合接口:参数配置、curl与Python工程化接入

黄金用量说明查询API聚合接口:参数配置、curl与Python工程化接入

适用场景与接口能力 在金融资讯应用、电商计价系统或个人投资分析工具中,实时获取黄金用量说明是一项常见需求。黄金用量说明数据通常分为国际贵金属报价、国内现货价以及各大品牌金店的零售价,来源分散、格式不统一。本文介绍的聚合查询API一次性提供上…

2026/7/22 12:50:04阅读更多 →
【AI工具月度复盘黄金流程】:20年SRE总监亲授——5步闭环法让团队提效47%(附可落地Checklist)

【AI工具月度复盘黄金流程】:20年SRE总监亲授——5步闭环法让团队提效47%(附可落地Checklist)

更多请点击: https://kaifayun.com 第一章:AI工具月度复盘流程的底层逻辑与价值锚点 AI工具月度复盘并非简单的使用时长统计或功能罗列,而是围绕“人—工具—目标”三角关系建立的动态校准机制。其底层逻辑根植于认知负荷理论与反馈闭环模型…

2026/7/22 13:52:17阅读更多 →
中职学校学工一体化平台选型指南:贴合职教管理业务的实用参考

中职学校学工一体化平台选型指南:贴合职教管理业务的实用参考

✅作者简介:合肥自友科技 📌核心产品:智慧校园平台(包括教工管理、学工管理、教务管理、考务管理、后勤管理、德育管理、资产管理、公寓管理、实习管理、就业管理、离校管理、科研平台、档案管理、学生平台等26个子平台) 。公司所有人员均有多…

2026/7/22 13:52:17阅读更多 →
参考文献格式错误率超41%?——用RAG+Schema-aware Parsing实现IEEE/AMA/APA一键合规(附可运行Python微服务脚本)

参考文献格式错误率超41%?——用RAG+Schema-aware Parsing实现IEEE/AMA/APA一键合规(附可运行Python微服务脚本)

更多请点击: https://intelliparadigm.com 第一章:AI搜索 AI搜索已从传统关键词匹配演进为语义理解与上下文感知的智能交互范式。它不再依赖精确的词序或布尔逻辑,而是通过大语言模型(LLM)和向量检索技术,…

2026/7/22 13:52:17阅读更多 →
AI节日视频生成器深度测评:5大平台实测对比,仅1家支持方言语音+动态水印+自动合规审核

AI节日视频生成器深度测评:5大平台实测对比,仅1家支持方言语音+动态水印+自动合规审核

更多请点击: https://intelliparadigm.com 第一章:AI节日视频生成器深度测评:5大平台实测对比,仅1家支持方言语音动态水印自动合规审核 在春节、中秋、国庆等重大节日期间,政务号、本地媒体及中小商家对定制化短视频需…

2026/7/22 13:52:17阅读更多 →
嵌入式开发实战:从基础到精通的技能进阶指南

嵌入式开发实战:从基础到精通的技能进阶指南

1. 嵌入式行业的残酷现实:为什么"半吊子"学不到真本事? 在嵌入式行业摸爬滚打十几年,我见过太多年轻人带着满腔热情入行,却在技术门槛前撞得头破血流。最近一位学员的遭遇特别典型:他花了半年时间学完STM32和…

2026/7/22 13:52:17阅读更多 →
�鸿蒙报错速查:@Trace �装在 V1 @Component 编译就炸,V2 装饰器只能装 @ComponentV2,根因 + 真解法

�鸿蒙报错速查:@Trace �装在 V1 @Component 编译就炸,V2 装饰器只能装 @ComponentV2,根因 + 真解法

报错原文 ERROR: 10505001 ArkTS Compiler Error Error Message: Trace can only be used in V2 components. At File: xxx.ets:N:N真机配图:V2 正解能编译能跑 Trace 装在 ComponentV2 里——正解,能编译能跑。改嵌套对象成员真触发 UI 重绘&#xff1a…

2026/7/22 13:50:17阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/22 0:53:59阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/22 0:53:59阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/22 0:53:59阅读更多 →
中小企业小程序开发公司怎么选:预算、上手和售后避坑指南

中小企业小程序开发公司怎么选:预算、上手和售后避坑指南

中小企业做小程序,最常见的矛盾是预算有限,但又不希望功能太单薄;没有技术团队,但又希望后续能自己运营;想快速上线,又担心隐性收费和售后失联。选型时如果只看“低价套餐”或“案例数量”,很容…

2026/7/22 0:01:17阅读更多 →
GEO优化如何沉淀长期内容资产?广拓时代谈AI搜索时代的内容ROI

GEO优化如何沉淀长期内容资产?广拓时代谈AI搜索时代的内容ROI

企业做营销,最怕钱花完了,资产没有留下。 效果广告能带来一段时间的曝光,但预算停止后,流量往往也随之停止。短视频内容可能在几天内冲高,也可能很快沉下去。AI搜索时代,企业需要重新思考一个问题&#xff…

2026/7/22 0:01:17阅读更多 →
Agent 终态判定:何时该停止思考、给出最终回复

Agent 终态判定:何时该停止思考、给出最终回复

Agent 终态判定:何时该停止思考、给出最终回复 一、你的 Agent 在"再想想"的循环里绕了 12 轮,用户已经关窗口了 Agent 与人最大的区别是:人知道什么时候该停下来给答案,Agent 会一直"想"下去。你给 Agent 接…

2026/7/22 0:01:17阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/21 22:53:50阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/21 18:53:30阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/21 18:53:30阅读更多 →