news 2026/4/23 13:02:17

毕业设计实战:从零构建一个高可用的刷题平台后端架构

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
毕业设计实战:从零构建一个高可用的刷题平台后端架构


毕业设计实战:从零构建一个高可用的刷题平台后端架构

摘要:许多学生在毕业毕业设计实战:从零构建一个高可用的刷题平台后端架构

摘要:许多学生在毕业设计中选择开发刷题平台,却常因缺乏工程经验而陷入性能瓶颈、接口混乱或数据一致性问题。本文基于真实毕业设计场景,详解如何使用 Spring Boot + MyBatis Plus + Redis 构建具备题目管理、用户提交、判题回调等核心功能的后端系统。通过引入消息队列解耦判题服务、利用 Redis 缓存热点题目、设计幂等性提交接口,显著提升系统吞吐量与稳定性。读者将获得一套可直接复用的模块化代码结构与部署 checklist。


1. 背景痛点:学生项目常见“三座大山”

毕业设计里做“刷题平台”听起来简单,落地时却常被以下问题卡住:

  1. 判题阻塞:同步判题导致线程长时间挂起,并发一上来整站 504。

  2. 重复提交:前端连点两下“提交”,数据库里出现两条记录,用户一脸懵。

  3. 冷启动延迟:题目列表接口每次全表扫描,首页打开 3 s 起步,答辩现场直接翻车。

这些痛点本质上是“学生项目”与“工程系统”之间的鸿沟:功能代码能跑,但缺容错、缺横向扩展、缺观测手段。下文用一套最小可用、却可线性扩展的架构,带你把“玩具”升级成“产品”。


2. 技术选型:为什么不是 Django,也不是本地内存

维度Spring BootDjango/Flask结论
依赖注入与 AOP原生支持靠第三方Spring 生态对事务、幂等、重试的封装更成熟
横向扩展无状态 Jar + 任意注册中心Python GIL 限制多进程利用率Java 多线程模型更适合 CPU 密集判题
社区组件MyBatis Plus、Spring Cloud、RocketMQ相对分散企业级方案直接搬来即用

缓存方案对比:

  • 本地内存:进程重启即失效,多实例时缓存漂移,无法横向扩展。
  • Redis:独立进程,可集群;支持 TTL、LRU、Pub/Sub,天然适合“热点题目”与“判题结果”缓存。

综上,后端主栈锁定Spring Boot 2.7 + MyBatis Plus + Redis 6.x + RocketMQ 4.9,部署在 2C4G 单机上即可抗住毕业设计答辩并发。


3. 模块划分与核心实现

系统分三层:网关层(Nginx)、业务层(Spring Boot)、判题层(Sandbox)。本文聚焦业务层,内部再拆为:

  • 题目服务(Problem Service)
  • 提交服务(Submit Service)
  • 判题回调(Judge Callback)

3.1 题目服务:缓存 + 分页 + 索引

热点题目(近 7 日提交量 Top 200)在 Redis 采用hash结构缓存,字段即题号,值序列化为 JSON;冷数据走 DB,分页用 MyBatis Plus 的Page对象。缓存穿透用布隆过滤器拦截,缓存雪崩加随机 60–120 s 的 TTL jitter。

3.2 提交服务:接口幂等性设计

前端提交时携带client_submit_id(UUID),后端用数据库唯一索引实现幂等:

UNIQUE KEY uk_user_submit (user_id, client_submit_id)

重复请求直接返回原结果,避免重复入库。核心代码见第 4 节。

3.3 判题回调:消息队列解耦

提交服务只负责“写记录 + 发消息”,不等待判题结果;Sandbox 判完后向 MQ 发送JudgeFinishedEvent,业务层消费后更新状态。事件体例如下:

{ "submitId": 142857, "result": "AC", "time": 120, "memory": 65536 }

消费端幂等:利用submitId做幂等键,更新前判断状态是否已终态(AC/WA/TLE 等),防止重复累加通过数。


4. 关键代码片段(含注释)

4.1 SubmitController——接收提交、幂等保护

@RestController @RequestMapping("/api/submit") @RequiredArgsConstructor public class SubmitController { private final SubmitService submitService; /** * 1. 幂等键:clientSubmitId * 2. 事务边界:仅落库与发消息,不等待判题 */ @PostMapping public ApiResult<SubmitDTO> submit(@LoginUser Long userId, @Valid @RequestBody SubmitRequest req) { // 重复提交直接返回 SubmitDTO exist = submitService.getByUserAndClientId(userId, req.getClientSubmitId()); if (exist != null) { return ApiResult.success(exist); } // 新提交:本地事务 = 写库 + 发 MQ SubmitDTO dto = submitService.doSubmit(userId, req); return ApiResult.success(dto); } }

4.2 JudgeEventConsumer——消费判题结果,保证幂等

@Component @RocketMQConsumer(topics = "topic_judge_result") @Slf4j @RequiredArgsConstructor public class JudgeEventConsumer { private final SubmitService submitService; private final RedisTemplate<String, String> redisTemplate; @Override public void onMessage(JudgeFinishedEvent event) { Long submitId = event.getSubmitId(); String key = "judge:result:" + submitId; // 1. 利用 Redis setnx 做分布式锁,防并发重复消费 Boolean absent = redisTemplate.opsForValue().setIfAbsent(key, "1", Duration.ofMinutes(5)); if (Boolean.FALSE.equals(absent)) { log.warn("duplicate consume, submitId={}", submitId); return; } // 2. 更新库,版本号乐观锁兜底 boolean updated = submitService.updateResult(event); if (!updated) { log.error("update submit result failed, event={}", event); } } }

5. 性能与安全:并发、防刷、SQL 注入

  1. 竞争条件
    更新提交状态时使用乐观锁version字段,CAS 失败重试 3 次,仍失败则日志告警人工介入。

  2. 防刷机制

    • 接口限流:基于 Redis 的令牌桶,每用户 10 次/60 s。
    • 验证码:同一 IP 5 min 内提交超过 20 次弹出图形验证码。
    • 代码相似度检测:引入sim命令,重复率 > 90 % 直接判 0 分并记录。
  3. SQL 注入
    MyBatis Plus 内置#{}预编译,杜绝拼接;动态排序用WrapperorderBy方法,内部白名单校验列名。


6. 生产级避坑 checklist

坑点现象解决
索引缺失题目列表按difficulty + create_time查询 2 s联合索引(difficulty, create_time)后降至 20 ms
判题超时无重试Sandbox 宕机,消息消费成功但结果丢失消费端 ack 前检查返回码,非 200 抛异常,MQ 自动重试 16 次
日志缺失线上出错无法复现接入traceId透传,Controller、MQ、线程池统一 MDC 打印
Redis 大 Key缓存整表select *导致 value 5 MB,网卡打满拆分为hash分片,只缓存必要字段
大事务提交接口里同步调用判题 + 写库 + 更新通过数,锁等待 3 s拆分为“写提交记录”与“更新通过数”两个事务,后者异步

7. 部署与可观测

  1. CI 脚本mvn -T 1C clean package -Dmaven.test.skip=true打出 fat-jar,配合systemd托管。
  2. Dockerfile仅 30 行,基于openjdk:17-jre-slim, layers 缓存缓存依赖。
  3. Prometheus + Grafana
    • JVM 级:GC、线程数、内存;
    • 应用级:QPS、RT、提交成功率;
    • 业务级:7 日 AC 率、题目冷热分布。
  4. 告警:RT > 1 s 持续 2 min 或错误率 > 5 % 即刻飞书群机器人推送。

8. 后续思考:如何支持多语言判题沙箱?

当前 Sandbox 只支持 C/C++,如果后续想扩展 Java、Python、Go,需要:

  1. 镜像隔离:用runsckata-containers替代裸docker run,防止ptrace逃逸。
  2. 资源配额:CPU、内存、seccomp 统一配置,不同语言复用同一套 cgroup 模板。
  3. MQ 路由:根据语言类型投递到不同 topic,消费端水平扩容互不干扰。
  4. 结果归一化:统一返回cpu_timememoryexit_code,业务层零改动。

整套代码已开源在 GitHub,欢迎 fork 并提交 Pull Request,一起把毕业设计项目做成能写进简历的工业级作品。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/1 15:42:48

车企智能客服系统实战:基于NLP与微服务架构的高并发解决方案

车企智能客服系统实战&#xff1a;基于NLP与微服务架构的高并发解决方案 摘要&#xff1a;车企智能客服面临高并发咨询、多轮对话理解等挑战。本文通过NLP意图识别、对话状态跟踪及微服务弹性伸缩方案&#xff0c;实现99.9%的意图识别准确率与5000 TPS的并发处理能力。包含Spri…

作者头像 李华
网站建设 2026/4/19 13:54:21

AI 辅助开发实战:高效完成 2025 计算机毕业设计的技术路径与避坑指南

毕业设计常见工程痛点 需求模糊&#xff1a;很多同学拿到题目时只有一句话&#xff0c;比如“做一个智能问答系统”&#xff0c;但具体支持什么题型、是否要多轮对话、要不要用户体系&#xff0c;全靠自己脑补。结果写到中期才发现功能膨胀&#xff0c;回炉重造。技术栈选择困…

作者头像 李华
网站建设 2026/4/18 5:25:43

Ubuntu22.04多版本CUDA部署实战:从11.8到12.1的平滑升级与兼容性验证

1. 为什么需要多版本CUDA共存 在深度学习开发中&#xff0c;不同框架对CUDA版本的要求往往存在差异。比如PyTorch 2.0推荐使用CUDA 11.8&#xff0c;而TensorRT 8.6则需要CUDA 12.1支持。更麻烦的是&#xff0c;某些遗留项目可能还依赖更早的CUDA版本。这就导致开发者经常需要在…

作者头像 李华
网站建设 2026/4/12 11:42:36

ChatGPT本地化部署实战:从模型加载到API封装的全流程解析

ChatGPT本地化部署实战&#xff1a;从模型加载到API封装的全流程解析 摘要&#xff1a;本文针对开发者面临的ChatGPT云端服务延迟高、数据隐私保护需求等痛点&#xff0c;详细解析如何通过LLaMA.cpp和FastAPI实现GPT模型的本地化部署。内容涵盖模型量化压缩、RESTful接口封装、…

作者头像 李华
网站建设 2026/4/20 1:23:19

深入解析PCIe BDF:Linux设备管理中的关键标识与应用实践

1. PCIe BDF基础概念&#xff1a;设备管理的身份证 第一次接触PCIe设备管理时&#xff0c;我盯着lspci命令输出的00:1f.0这样的字符串发呆了半天。后来才知道&#xff0c;这串看似简单的编码其实是PCIe世界的"身份证号"&#xff0c;专业术语叫做BDF&#xff08;Bus:…

作者头像 李华
网站建设 2026/4/18 12:45:32

ChatGPT Unable to Load Conversation 问题分析与实战解决方案

ChatGPT Unable to Load Conversation 问题分析与实战解决方案 线上环境最怕用户突然甩来一句&#xff1a;“刚才聊的内容怎么没了&#xff1f;”——刷新页面后只剩 Unable to load conversation&#xff0c;后台日志里却躺着 503、429、504 轮番蹦迪。 本文把过去三个月踩过的…

作者头像 李华