作业批改系统高并发改造与 OpenClaw 集成实战
概述
今天对作业批改系统进行了一次全面改造,解决了三大问题:高并发下的 AI 批改瓶颈、数据库查询性能、以及 OpenClaw OneBot 插件的消息分发故障。本文记录完整的排障过程和解决方案。
一、异步批改:从同步阻塞到 Redis Stream
问题
原系统 HomeworkController.submitHomework() 的流程是:
1 | 学生提交 → Controller 线程阻塞等待 OpenClaw AI 批改(30-300s) → 写DB → 返回 |
30 个学生同时提交 → 30 个 Tomcat 线程全阻塞 → 新请求被拒绝 → 数据库连接池(HikariCP max 20)耗尽 → 雪崩。
方案
参考 [InterviewGuide] 的 Redis Stream 异步架构:
1 | 提交 → 入库(PENDING) → 入队(Redis Stream) → 秒回"已收到" |
实现
新增 5 个文件:
| 文件 | 作用 |
|---|---|
config/RedisConfig.java |
RedissonClient Bean |
common/async/AsyncTaskConstants.java |
Stream Key、重试次数常量 |
common/async/GradingStreamProducer.java |
生产者:入队 / 重试入队 |
common/async/GradingStreamConsumer.java |
消费者:单线程消费 → 调AI → 写DB → ACK |
Submission.java |
加 status、errorMessage 字段 |
关键代码变更:
1 | // HomeworkController.submitHomework() — 原来同步阻塞 300 秒 |
前端 PageOne.vue 同样改造:提交后轮询 GET /api/homework/result/{id},展示 PENDING → PROCESSING → COMPLETED 状态。
坑点:redisson-spring-boot-starter 依赖 spring-boot-starter-data-redis(项目未引入),改为纯 redisson 即可。
二、数据库查询性能优化
问题
Dashboard 仪表盘等页面存在大量 SELECT * 查询,每次都拉取了 raw_response 字段(每条 10-50KB 的 AI 返回 JSON)。
假设班级 50 人,每人 3 份作业(150 条),getMetrics() 一次就传输约 7MB 无用数据。
修复清单
| 方法 | 问题 | 修复 |
|---|---|---|
getMetrics() |
SELECT * 只为算平均分 |
SELECT total_score 只拉一个 int |
getFrequentErrors() |
SELECT * 只为解析错误 |
SELECT raw_response 只拉一个字段 |
getStudentOverview() |
N+1:每人逐条查 SELECT * |
GROUP BY user_id 批量聚合 1 次 |
getScoreDistribution() |
每份提交都计入,一人 3 次算 3 个数据点 | 改为按学生平均分统计 |
TaskController.getTasks() |
每个作业查一次 SELECT * FROM submission |
SELECT task_id, COUNT, AVG ... GROUP BY task_id 批量 1 次 |
ChatHistoryMapper.selectByUserId() |
无 LIMIT,重度用户 5000 条 → 5MB | 加 LIMIT 200 |
SubmissionMapper 学生进度(2个) |
s.* 含无用 raw_response |
只选需要的 18 列 |
前端 PageFive.vue 的 loadTasks() 从串行 for...await 改为 Promise.allSettled 并行请求。
新增 5 个专项查询方法:selectScoresByClassId、selectRawResponsesByClassId(×2)、selectStudentStatsByClassId、selectTaskStatsByClassId。
效果:
| 指标 | 之前 | 之后 |
|---|---|---|
getMetrics() 数据传输 |
~7MB | ~2KB |
getFrequentErrors() SQL |
拉全表 | 只拉 raw_response |
getStudentOverview() SQL 次数 |
51 次 | 2 次 |
三、C 语言批改 Skill 规范化
问题
OpenClaw 的 homework-grader skill 原本面向 Java 通用编程,知识点评分每次名称不一致,导致 Dashboard 的热力图和高频错题每次刷新都不一样。
修复
SKILL.md 重写(~/.openclaw/workspace/skills/homework-grader/SKILL.md):
- 知识点封闭化 — 按谭浩强《C语言程序设计》第五版第 1-10 章目录列出,AI 必须从列表中选择
- 分级扣分体系:
| 级别 | severity | 单次扣分 | 示例 |
|---|---|---|---|
| 致命 | critical | 10-20 | 无法编译、核心逻辑错误 |
| 主要 | major | 5-10 | 输出错误、内存泄漏、数组越界 |
| 轻微 | minor | 1-3 | 命名不规范、缺少注释、缩进问题 |
- C 语言专项 errors.type:语法错误、逻辑错误、内存错误、指针错误、输入输出、规范问题、安全问题
- 一致性约束:同份作业多次批改 totalScore 偏差 ≤ ±5 分,同类 minor 问题最多扣 3 次
- 20+ 个具体 C 场景扣分标准:
=错写==(-5)、malloc 无 free(-5)、scanf 缺 &(-5) 等
后端归一化(DashboardServiceImpl.java):
新增 KP_CANONICAL 映射表,100+ 别名统一归一,例如:
1 | KP_CANONICAL.put("指针", "指针变量"); |
getKnowledgeMastery() 在统计前先做名称归一化,确保不同批改返回的同义词合并。
四、OpenClaw OneBot 插件排障
问题
QQ 群 @机器人 后,WebChat 控制台的 session 里能看到 agent 回复,但 QQ 群收不到消息。
日志关键线索:
1 | dispatchReplyWithBufferedBlockDispatcher returned successfully. |
排查过程与根因
| # | 尝试 | 结果 |
|---|---|---|
| 1 | ctxPayload 缺少 SessionKey |
加上后仍不行 |
| 2 | disableBlockStreaming: true |
仍然不行 |
| 3 | main agent 能回复,jarvis 不能 | 锁定 jarvis 专属问题 |
| 4 | 日志发现 agentDir 导致 embedded-run |
去掉 agentDir |
| 5 | 去掉 workspace 测试 |
仍不行 |
| 6 | 日志发现每次 jarvis 处理时插件都重载 | 根因! |
1 | 22:45:00 dispatching message for session agent:jarvis:onebot:group:875860223 |
OneBot 插件在 jarvis 消息处理时被 plugins.allow 为空触发的自动扫描重载了。旧的 dispatcher 回调被销毁,agent 的流式回复到达时没有接收者。
最终修复
openclaw.json加"plugins": { "allow": ["openclaw-onebot"] }— 阻止自动扫描process-inbound.js加SessionKey: sessionId— dispatcher 路由修正- jarvis 配置 去掉
agentDir,保留workspace— 避免 embedded-run 隔离
五、经验总结
- AI 调用必须异步化 — 同步等待几十到几百秒是并发杀手,Redis Stream 是最轻量的解耦方案
- 永远不要
SELECT *— 大字段(raw_response)是数据库性能的头号杀手 - 批量聚合优于 N+1 —
GROUP BY一次查询替代循环逐条查 - AI 输出需要后端归一化 — 自由文本的知识点评分必须映射到规范名称
- 插件重载是隐蔽炸弹 —
plugins.allow显式声明可以避免很多诡异问题 - 分而治之排障 — main agent 能工作 jarvis 不能 → 锁定配置差异 → 逐项排除
相关代码
- 后端项目:
F:\firedemo\demo - 前端项目:
F:\firedemo\vue-project - OpenClaw Skill:
~\.openclaw\workspace\skills\homework-grader\SKILL.md - OneBot 插件:
C:\Users\LKL\.openclaw\extensions\openclaw-onebot - Gateway 配置:
C:\Users\LKL\.openclaw\openclaw.json





