1. 项目背景与核心价值
去年团队内部技术考核时,我们遇到一个尴尬局面:新人提交的代码在本地测试通过,但上线后频频出现边界条件错误。这件事让我意识到,传统的"布置题目+邮件提交"的刷题模式存在三个致命缺陷:
- 测试用例覆盖不全(新人往往只测试happy path)
- 反馈周期长(需要人工批改)
- 缺乏数据沉淀(无法量化成长曲线)
这个用Spring Boot + React打造的刷题系统,正是为了解决这些痛点而生。经过三个迭代周期,1.0.0版本已经实现了从题目管理、在线判题到学习分析的全流程自动化。特别值得一提的是判题模块,通过Docker沙箱隔离+多维度测试用例验证,能将代码缺陷的发现时机从"上线后"提前到"提交时"。
2. 系统架构设计
2.1 技术栈选型
后端采用经典的Spring Boot全家桶:
- Web层:Spring MVC + RESTful API
- 安全:Spring Security + JWT
- 数据:MyBatis-Plus + MySQL 8.0
- 判题引擎:Docker Java API + Judge0二次开发
前端选用React生态:
- 状态管理:Redux Toolkit
- UI库:Ant Design Pro
- 图表:ECharts
技术选型心得:初期考虑过Python+Django快速原型,但Java的线程池管理和Docker SDK成熟度最终胜出。React的选择则源于团队现有技术栈的统一。
2.2 核心模块交互
系统采用微服务架构拆分:
code复制用户服务 → 题目服务 → 判题服务 → 日志服务
↑ ↓
管理后台 ← 数据分析服务
关键设计决策:
- 判题服务独立部署,避免CPU密集型操作影响主业务
- 使用RabbitMQ实现异步判题,支持峰值削谷
- Redis缓存高频访问的题目数据
3. 核心功能实现
3.1 题目管理模块
采用JSON Schema定义题目元数据:
json复制{
"type": "coding",
"difficulty": "medium",
"tags": ["DFS", "回溯"],
"timeLimit": 2000,
"memoryLimit": 256,
"testCases": [
{
"input": "[[1,2],[3,4]]",
"output": "[3,7]",
"score": 30
}
]
}
开发中遇到的坑:
- 最初将测试用例存在MySQL的TEXT字段,JSON解析性能差
- 改进方案:改用MongoDB存储非结构化数据,查询速度提升8倍
3.2 在线判题引擎
判题流程的六个关键步骤:
- 用户代码写入临时文件
- 生成Docker容器(限制CPU/内存)
- 编译阶段(如Java需先javac)
- 逐条运行测试用例
- 收集运行指标(时间/内存/覆盖率)
- 生成判题报告
安全防护措施:
- 使用--read-only挂载文件系统
- 禁用容器网络访问
- 白名单限制系统调用
3.3 学习数据分析
基于ELK技术栈实现:
- Logstash收集用户行为日志
- Elasticsearch聚合学习数据
- Kibana展示个人成长曲线
典型分析维度:
markdown复制| 指标 | 计算方式 | 应用场景 |
|-----------------|----------------------------|-----------------------|
| 通过率 | 通过数/提交数 | 题目难度校准 |
| 缺陷模式 | 错误类型聚类 | 针对性训练建议 |
| 时间分布 | 提交时段热力图 | 学习习惯分析 |
4. 性能优化实战
4.1 判题服务压测
使用JMeter模拟的瓶颈点:
- 并发50请求时Docker创建延迟突增
- 频繁的容器启停导致宿主机负载高
优化方案:
- 引入容器池预初始化技术
- 改用gVisor代替完整Docker(减少30%内存占用)
- 增加判题节点自动扩容策略
4.2 数据库调优
通过EXPLAIN发现的慢查询:
sql复制SELECT * FROM problems
WHERE tags LIKE '%DFS%'
ORDER BY create_time DESC
优化措施:
- 改为JSON字段索引(MySQL 8.0+)
- 增加复合索引(tags + difficulty)
- 引入ES实现高级搜索
5. 部署方案
5.1 生产环境配置
使用Docker Compose编排:
yaml复制services:
judge-worker:
image: openjdk:17-jdk
deploy:
resources:
limits:
cpus: '2'
memory: 4G
environment:
- DOCKER_HOST=tcp://host.docker.internal:2375
关键配置项:
- 每个判题容器限制1核CPU/512MB内存
- 设置Swappiness=10避免过度交换
- 日志轮转策略按500MB分割
5.2 监控告警
Prometheus监控指标示例:
promql复制rate(judge_time_seconds_sum[5m])
/
rate(judge_time_seconds_count[5m])
告警规则:
- 连续5分钟CPU>80%
- 判题超时率>5%
- 容器启动失败次数突增
6. 典型问题排查
6.1 内存泄漏问题
现象:判题服务运行24小时后响应变慢
诊断过程:
- jmap发现DockerClient实例堆积
- 追溯代码发现未关闭Docker连接
- 引入try-with-resources模式修复
6.2 并发冲突案例
用户反馈提交结果错乱:
- 根源:判题回调接口未做幂等处理
- 复现:快速连续提交相同代码
- 解决:增加Redis分布式锁
7. 扩展方向
当前正在开发的进阶功能:
- AI辅助判题(使用CodeBERT检测代码异味)
- 竞赛模式(限时编程+实时排名)
- 智能推荐(基于历史表现的题目推荐)
踩过的坑让我深刻认识到:判题系统的核心不是技术复杂度,而是对开发者体验的深度理解。比如在错误提示上,我们迭代了5个版本才找到平衡点——既要暴露足够调试信息,又要避免泄露测试用例细节。