news 2026/5/24 7:30:42

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

作者头像

张小明

前端开发工程师

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/5/1 10:31:47

Conda Prompt路径切换实战:高效管理Python环境的避坑指南

背景痛点&#xff1a;手动切路径到底有多痛 日常开发里&#xff0c;我平均一天要切五六次 conda 环境。每次切完还得手动 cd 到项目目录&#xff0c;三步之外必踩坑&#xff1a; 激活延迟 在 Windows 上&#xff0c;conda activate 平均 1.2 s&#xff0c;PowerShell 还要再慢…

作者头像 李华
网站建设 2026/5/19 16:48:46

ChatGPT写引言实战指南:如何高效生成技术文档开篇

技术文档引言写作的三大痛点 写技术文档时&#xff0c;最常被卡住的其实是第一段——引言。 要交代背景&#xff0c;又不能啰嗦&#xff1b;要出现关键术语&#xff0c;还得保证准确&#xff1b;要面向不同角色&#xff08;开发、运维、产品&#xff09;&#xff0c;却只能用…

作者头像 李华
网站建设 2026/5/1 10:29:50

从零搭建自主交通智能客服系统:技术选型与实战避坑指南

从零搭建自主交通智能客服系统&#xff1a;技术选型与实战避坑指南 摘要&#xff1a;本文针对交通行业客服系统智能化转型中的技术选型复杂、对话理解准确率低、系统集成困难等痛点&#xff0c;详细解析基于NLP和微服务架构的自主交通智能客服搭建方案。通过对比主流NLP引擎性能…

作者头像 李华
网站建设 2026/5/15 18:15:33

CosyVoice 实战优化:从架构设计到性能调优的全链路解析

背景痛点&#xff1a;实时语音流里的“慢”与“碎” 第一次把 CosyVoice 塞进生产环境时&#xff0c;我对它的官方 benchmark 信心满满——单核 0.8RTF&#xff0c;16 核理论 12 实时。结果上线第二天&#xff0c;高峰并发一冲&#xff0c;CPU 利用率飙到 90%&#xff0c;延迟…

作者头像 李华
网站建设 2026/5/12 3:03:27

钉钉接入Dify工作流实现智能客服问答的技术实现与优化

背景与痛点 传统客服系统普遍采用“人工坐、工单转、知识库查”三段式流程&#xff0c;面对瞬时高并发咨询时&#xff0c;暴露出以下典型瓶颈&#xff1a; 响应延迟&#xff1a;人工坐席数量有限&#xff0c;排队机制导致平均等待时间超过30秒&#xff0c;夜间时段无人值守&a…

作者头像 李华