1. 测试背景
上一轮完成了题目查询、发布、判题结果和历史记录的核心业务回归。本轮进一步关注异步处理中的几个问题:
- 提交请求返回成功后,后台任务是否真的完成?
- 用户重复点击或网络重试,会不会创建重复任务?
- 后台暂时停止处理时,任务是否能够保留?
- 服务恢复后,原来的任务是否还能继续完成?
本轮围绕这些问题,完成正常异步处理、请求幂等、Worker 暂停恢复和 Web 重启后的任务保留验证。测试以接口响应、任务编号和数据库状态为主要证据,不进行性能压测,也不覆盖强杀进程、数据库宕机等复杂故障。
2. 先理解平台的异步判题流程
当前 OJ 将接收提交与后台判题分开处理:
用户提交代码 ↓ Web 服务检查并保存任务 ↓ 返回 202 和任务编号 ↓ 任务保存在 MySQL 队列中 ↓ Worker 领取任务 ↓ 编译服务编译、执行并比较结果 ↓ Worker 保存最终结果 ↓ 网页查询并展示结果各组件的职责如下:
| 组件 | 主要职责 |
|---|---|
| Web 服务 | 接收提交、检查参数和身份、提供任务查询 |
| MySQL | 保存排队任务、代码、题目快照、状态和结果 |
| Worker | 领取任务,调用编译服务,保存结果 |
| 编译服务 | 编译代码、执行程序、生成判题结果 |
| 网页 | 提交任务,轮询状态,展示最终结果 |
这里采用 MySQL 保存任务队列,没有使用 Redis 或 RabbitMQ。
异步判题并不代表答案会立刻返回,而是提交接口先完成“任务受理”,后台再按处理能力执行。这样可以避免提交请求一直等待编译运行,也便于分别维护 Web 和 Worker。
3. 测试思路与范围
本轮从四个方向进行检查:
| 方向 | 主要问题 | 验证思路 |
|---|---|---|
| 正常异步处理 | 受理后能否得到最终结果? | 提交后按任务编号查询,等待终态 |
| 请求幂等 | 重复发送会不会多建任务? | 重放相同请求,比较返回编号 |
| 暂停处理 | Worker 停止后任务会不会丢失? | 停止 Worker,再提交并检查排队状态 |
| 恢复处理 | 服务恢复后原任务能否完成? | 重启 Web、恢复 Worker,持续核对原任务 |
恢复测试安排在自己使用的环境中进行,暂停 Worker 时不同时执行其他回归或压测。
4. 正常异步处理:受理不等于完成
提交代码后,接口返回 HTTP 202,表示服务器已经受理任务。此时不能直接判断“判题成功”,还需要根据任务编号继续查询。
任务状态包括:
| 状态 | 含义 |
|---|---|
| QUEUED | 已保存,等待处理 |
| RUNNING | 后台正在处理 |
| FINISHED | 已得到判题结论 |
| ERROR | 系统处理故障或任务中断等异常 |
其中,FINISHED 不一定是答案正确。WA、CE、RE、TLE 等也可以是正常完成判题后的结论。
本轮正常场景使用正确参考代码,重点检查:
- 提交返回 202,并提供任务编号和查询地址。
- 后续查询始终针对同一个任务。
- 最终状态为 FINISHED,判题结果为 AC。
- 状态查询响应不包含提交代码或题目内部快照。
快速任务可能直接被查询到完成状态,因此没有要求每次都必须观察到 RUNNING。判断依据是任务最终状态和结果,而不是是否截取到了每个中间阶段。
5. 请求幂等:重复发送仍对应同一个任务
5.1 为什么需要幂等
考虑以下情况:
网页提交代码 ↓ 服务器已经创建任务 ↓ 网络异常,网页没有收到响应 ↓ 用户点击重试如果每收到一次请求就创建新任务,同一次提交可能被重复判题。
因此,客户端为一次提交生成request_id。重试原请求时,继续使用相同编号和内容,服务器返回原任务。
5.2 本轮检查的几种情况
| 请求情况 | 预期 |
|---|---|
| 同账号、同编号、同内容再次发送 | 返回原任务编号,标记为复用 |
| 同账号、同编号,但修改代码或输入 | 返回 409,幂等冲突 |
| 多个相同请求并发到达 | 返回同一个任务编号 |
| 不同账号使用相同编号 | 分别创建各自的任务 |
本轮使用 6 个并发相同请求,检查返回的任务编号是否一致。这是在验证并发条件下的去重正确性。
相同代码使用新的请求编号,仍然可以创建新提交。系统去重的是“同一次请求”,不是禁止用户反复提交同一份代码。
另外,客户端幂等键request_id与响应中的X-Request-ID用途不同:前者标识一次业务提交,后者用于关联单次 HTTP 请求日志。
6. Worker 暂停:任务应保留在队列中
Worker 负责领取和处理任务。正常停止 Worker 后,Web 和数据库仍然运行,因此提交接口仍可接收符合条件的任务。
本轮在 Worker 停止后,使用专用测试账号提交任务:
- 前 4 个任务返回 202;
- 任务状态为 QUEUED;
- 第 5 个新任务返回 429,业务码为
TOO_MANY_PENDING; - 重放第一条已受理请求,仍能返回原任务,而不是再创建一条。
这里的 429 表示达到单用户待完成任务数量限制,与账号模块每分钟认证次数限制不同。
这个场景同时检查了两件事:
- 后台暂时不处理时,任务能够保存并排队。
- 系统不会无限接收同一用户的新任务,但已有任务的重试仍可正常查询和复用。
通过数据库只读查询,进一步核对本轮四条任务确实存在,且编号与接口返回一致、状态为 QUEUED。
7. Web 重启:已受理任务应继续存在
保持 Worker 停止,再重启 Web 服务。
这样安排是为了避免任务在验证期间被后台快速处理掉,便于对比重启前后的排队记录。
Web 恢复就绪后,重新登录测试账号,重放原来的提交请求并查询任务,检查:
- 返回的仍然是原任务编号;
- 没有因为重放创建新任务;
- 原任务仍处于 QUEUED;
- 数据库中的任务归属、题目版本和创建时间等信息保持不变。
排队耗时会随着时间增加,因此没有将它作为“重启前后必须完全相同”的字段。
这一场景验证的是:任务已经保存在数据库中,不依赖 Web 进程内存维持。Web 重启后,仍可以读取和操作原有任务记录。
8. 恢复 Worker:原任务应完成
重新启动 Worker 后,继续检查前面保存的同一批任务。
恢复成功的判断不能只看服务变为 active,还需要确认真实业务恢复:
- 原任务能够被 Worker 领取。
- 查询的仍然是原任务编号。
- 四条任务最终均为 FINISHED,判题结果为 AC。
- 数据库中开始时间和完成时间有值。
- Web、Worker、编译服务和数据库恢复正常运行。
本轮使用正确参考代码,因此最终预期为 AC。如果使用错误答案,恢复成功也可能对应 WA,不能把所有场景的恢复标准都固定为答案正确。
9. 测试结果与结论边界
本轮正常异步、请求幂等、Worker 暂停、Web 重启和 Worker 恢复检查均按预期通过。
通过这组测试,验证了:
- 提交受理与最终结果查询能够正确衔接;
- 相同业务请求可以复用原任务;
- 修改内容复用请求编号时能够识别冲突;
- 小规模并发重复请求不会产生不同任务编号;
- Worker 停止期间,任务能够保留在数据库中;
- Web 重启后,原排队任务仍可查询和复用;
- Worker 恢复后,原任务能够继续处理并完成。
需要明确的是,本轮采用正常停止和启动服务,不代表已经验证进程崩溃、数据库故障、网络分区或机器掉电等情况,也不能据此宣称实现了任何故障下的“恰好执行一次”。
此外,Web 的就绪接口主要检查 Web 与数据库。Worker 停止时,该接口仍可能返回就绪,因此还需要结合 Worker 状态、队列变化和真实任务结果判断判题能力是否恢复。
后续性能测试将继续区分任务受理速度、排队时间和最终完成能力,避免只看提交接口返回快,就认为整个判题系统性能良好。