news 2026/7/31 21:18:08

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

作者头像

张小明

前端开发工程师

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/7/27 15:29:41

实战指南:如何用C++构建高效语音助手插件(附主流方案对比)

背景痛点&#xff1a;C语音助手插件到底难在哪 做语音助手插件&#xff0c;最难的不是“让AI说话”&#xff0c;而是“让AI在正确的时间听到正确的话”。 我去年给一款桌面工具加语音唤醒&#xff0c;踩坑踩到怀疑人生&#xff0c;总结下来就三句话&#xff1a; 音频采集延迟…

作者头像 李华
网站建设 2026/7/28 17:25:50

ChatGPT翻译论文指令实战指南:从精准调参到学术合规

ChatGPT翻译论文指令实战指南&#xff1a;从精准调参到学术合规 学术翻译场景到底难在哪 写论文时&#xff0c;我们最怕的不是英文不好&#xff0c;而是“词对了&#xff0c;味不对”。学术文本有三个隐形门槛&#xff1a; 术语一致性&#xff1a;同一关键词前后必须同译&am…

作者头像 李华
网站建设 2026/7/26 20:33:26

AI辅助开发实战:基于cosyvoice 2的音色替换技术实现与优化

把笔记本摊开&#xff0c;先给自己冲一杯速溶咖啡——接下来两个小时&#xff0c;我们要把一段平平无奇的 TTS 语音&#xff0c;换成“隔壁主播”的磁性嗓音&#xff0c;还要让它在 200 并发下跑进 300 ms 以内。同一个需求&#xff0c;去年我用传统拼接法折腾了 3 周&#xff…

作者头像 李华
网站建设 2026/7/31 21:15:53

大数据毕设招聘项目实战:从需求分析到高可用架构落地

大数据毕设招聘项目实战&#xff1a;从需求分析到高可用架构落地 关键词&#xff1a;大数据毕设招聘、Flink、Kafka、Elasticsearch、事件驱动、幂等写入 一、典型痛点&#xff1a;为什么“招聘”场景总被毕设“劝退” 去年指导学弟做“校招数据分析”时&#xff0c;他第一句话…

作者头像 李华