
一、为什么平时不卡考试最后5分钟却容易卡假设一场考试有1000名考生考试时长60分钟。在前55分钟内考生的操作通常比较分散有人正在阅读题目有人在切换题目有人修改答案有人停留思考有人提前交卷。此时请求会分布在较长的时间范围内服务器虽然持续收到访问但整体压力相对平稳。到了最后5分钟考生行为开始趋同系统弹出剩余时间提醒大量考生开始检查未答题目频繁切换题目并修改答案连续触发自动保存部分考生主动点击交卷倒计时结束后未交卷人员被统一自动交卷。这时系统面对的不是简单的“1000人在线”而是1000人在短时间内执行相似的高成本操作。尤其是交卷操作通常需要完成以下流程校验考试是否仍然有效校验考生身份和考试资格获取考生全部答案检查答案完整性写入最终答卷修改考试状态计算客观题成绩保存题目得分生成考试成绩更新参考人数、交卷人数等统计数据记录交卷日志和防作弊日志向前端返回交卷结果。如果这些操作全部放在一个同步请求中完成大量交卷请求同时到达后就容易形成数据库锁等待、连接池耗尽、线程堆积和请求超时。二、集中交卷最常见的五个性能瓶颈1. 最后一刻才上传整张答卷一些考试系统只在考生点击“交卷”时才把全部答案一次性提交到服务器。假设一张试卷有100道题每道题还包含作答内容、题目标记、作答时间和附件信息那么一次交卷请求的数据量可能比较大。当大量考生同时上传完整答卷时会同时占用网络带宽Web服务器线程请求解析资源数据库连接磁盘写入能力。这种设计平时看不出问题一旦集中交卷就会迅速放大。2. 交卷和阅卷完全同步执行有些系统要求交卷接口必须完成全部阅卷和统计后才向考生返回结果。例如考生点击交卷后系统同步完成客观题判分题目得分明细保存总分计算通过状态判断排名计算部门统计知识点正确率统计。这些计算如果全部在交卷事务中执行会显著拉长接口响应时间。一个请求原本只需要200毫秒加入阅卷和统计后可能增长到数秒。大量慢请求同时存在很容易占满服务器工作线程。3. 数据库事务过大部分系统会在一个数据库事务中完成答案保存、成绩生成、试题统计、考试统计和人员档案更新。事务越大持有锁的时间越长。当多名考生同时更新相同考试的统计记录例如“已交卷人数”或“参考人数”时还可能竞争同一行数据形成热点更新。4. 考生重复点击交卷交卷后页面没有及时响应考生通常会再次点击。在网络抖动情况下浏览器或网关也可能自动重试请求。于是同一名考生的一次交卷行为可能对应两次、三次甚至更多次请求。如果接口没有做好幂等控制就可能出现生成两条成绩记录重复扣除考试次数重复发送证书多次更新统计人数第一次交卷成功第二次却返回系统异常。5. 倒计时结束后统一自动交卷如果1000名考生的浏览器都在同一秒触发自动交卷相当于系统主动制造了一次流量洪峰。更危险的是浏览器时间可能不准确前端页面也可能已经断网或关闭。因此不能把考试结束控制完全依赖于客户端倒计时。三、解决集中交卷问题的核心思路一个稳定的在线考试系统不应把所有压力都集中到“交卷”这一个动作上。更加合理的设计是作答过程中持续保存交卷时只确认状态系统先可靠接收再异步完成后续处理。整体流程可以拆分为四个阶段作答阶段持续保存答案交卷接口快速生成接收凭证后台异步完成阅卷和统计异常任务通过补偿机制恢复。也就是说交卷接口最重要的职责不是立即完成所有业务而是保证考生的答案没有丢失交卷请求已经被可靠接收同一答卷不会重复处理后续任务能够继续执行。四、第一层削峰将答案保存分散到考试过程中1. 自动保存不能只设置固定时间最简单的自动保存方式是每隔30秒保存一次答案。但如果所有考生都在进入考试后同时开始计时那么他们也可能在同一时间触发保存仍然会形成周期性峰值。更合理的方式是采用“事件触发定时兜底随机抖动”考生修改答案后延迟几秒保存切换题目时保存当前题答案每隔一定时间执行一次兜底保存不同客户端增加少量随机延迟避免请求完全同步。例如自动保存周期可以设置为25至35秒之间的随机时间而不是所有人固定30秒。2. 只保存发生变化的答案每次自动保存时不需要上传整张试卷。可以为每道题维护一个版本号或修改标记只提交发生变化的题目。请求示例{ examId: 10001, attemptId: 820056, clientVersion: 18, answers: [ { questionId: 501, answer: B, answerVersion: 3 }, { questionId: 502, answer: A,C, answerVersion: 2 } ] }这样可以明显减少网络传输量和数据库写入量。3. 服务端保存最后版本服务端不能简单覆盖答案而应根据版本号判断新旧。简化SQL逻辑如下UPDATE exam_answer SET answer_content ?, answer_version ?, update_time NOW() WHERE attempt_id ? AND question_id ? AND answer_version ?;只有客户端提交的版本号大于数据库版本号时才允许更新。这样可以避免网络延迟导致旧答案覆盖新答案。五、第二层削峰交卷请求与阅卷任务解耦1. 交卷接口只做关键操作交卷接口应尽量缩短同步处理链路。建议同步完成以下操作校验考生和答卷确认考试是否允许交卷固化最终答案版本将答卷状态修改为“已接收”生成唯一交卷凭证写入阅卷任务返回交卷已接收。而以下操作可以异步执行客观题评分主观题待阅卷任务生成成绩汇总排名计算部门统计知识点分析证书生成个人档案更新。考生点击交卷后可以先看到“您的答卷已成功提交系统正在处理成绩。”而不是要求用户一直等待全部统计完成。2. 使用消息队列削峰当大量交卷请求进入系统后可以先把阅卷任务写入消息队列再由后台消费者按照系统处理能力逐步执行。逻辑流程如下考生交卷 ↓ 交卷接口校验 ↓ 保存交卷凭证 ↓ 写入阅卷任务队列 ↓ 立即返回“交卷已接收” ↓ 后台异步阅卷 ↓ 生成成绩和统计数据即使短时间内产生1000个交卷任务也不必同时启动1000次完整阅卷。系统可以根据服务器配置启动固定数量的消费者例如同时处理20个或50个阅卷任务其余任务在队列中等待。这就是典型的削峰填谷。六、交卷幂等重复请求只能产生一个结果削峰解决的是“请求太多”幂等解决的是“同一请求被处理多次”。1. 什么是交卷幂等交卷幂等是指同一名考生对同一场考试无论点击多少次交卷系统最终都只生成一份有效答卷和一条有效成绩。第一次请求负责真正完成交卷。后续重复请求不再重复执行业务而是直接返回第一次交卷的结果。2. 生成唯一幂等键可以使用以下字段组成幂等键examId attemptId或者examId userId examRecordId其中attemptId应在考生进入考试时生成并且在本次考试过程中保持不变。例如SUBMIT:10001:8200563. 数据库唯一约束是最终保障Redis分布式锁可以减少重复请求但不能作为唯一保障。因为Redis锁可能因超时、网络异常或服务重启而失效。真正可靠的幂等控制仍然要落到数据库唯一约束和条件更新上。例如在交卷记录表中增加唯一索引ALTER TABLE exam_submit_receipt ADD UNIQUE KEY uk_attempt_id (attempt_id);插入交卷凭证时INSERT INTO exam_submit_receipt (attempt_id, submit_no, submit_time) VALUES (?, ?, NOW());如果同一个attemptId再次插入数据库会直接拒绝重复记录。4. 使用状态机控制答卷流转答卷状态不应只有“未交卷”和“已交卷”建议设计为ANSWERING作答中 SUBMITTING交卷处理中 RECEIVED交卷已接收 SCORING正在阅卷 SCORED阅卷完成 FAILED处理异常状态变更必须按照固定方向进行不能随意回退。例如交卷时使用条件更新UPDATE exam_attempt SET status RECEIVED, submit_time NOW() WHERE id ? AND status IN (ANSWERING, SUBMITTING);程序需要检查受影响行数。如果受影响行数为1说明本次请求完成了状态变更。如果受影响行数为0说明答卷可能已经交卷系统应查询原交卷结果并返回而不是再次处理。5. Java简化示例以下代码只用于说明幂等思路Transactional public SubmitResult submitExam(Long attemptId) { ExamAttempt attempt examAttemptRepository.findById(attemptId); if (attempt null) { throw new BusinessException(答卷不存在); } if (attempt.isReceivedOrScored()) { return submitReceiptRepository.findResultByAttemptId(attemptId); } int affectedRows examAttemptRepository.markAsReceived(attemptId); if (affectedRows 0) { return submitReceiptRepository.findResultByAttemptId(attemptId); } SubmitReceipt receipt submitReceiptRepository.createIfAbsent(attemptId); scoringTaskRepository.createIfAbsent(attemptId); return SubmitResult.accepted(receipt.getSubmitNo()); }这里至少有三层保障业务状态判断条件更新数据库唯一约束。即使两个请求几乎同时到达也只有一个请求能够真正完成状态变更。七、不要在交卷事务中实时更新全部统计不少系统会在每个考生交卷时立即执行UPDATE exam_statistics SET submit_count submit_count 1 WHERE exam_id ?;1000名考生同时交卷就会同时竞争同一条考试统计记录。这种“热点行更新”很容易造成锁等待。更加合理的方式有两种。方案一交卷事件异步汇总每次交卷只记录事件不直接修改总统计。后台任务定期汇总SELECT COUNT(*) FROM exam_attempt WHERE exam_id ? AND status IN (RECEIVED, SCORING, SCORED);方案二分片计数按照考生ID或答卷ID将计数拆分到多个分片中exam_count_0 exam_count_1 exam_count_2 …… exam_count_15查询总数时再对多个分片求和。对于一般企业考试系统异步汇总通常已经能够满足需求结构也更加简单。八、自动交卷不能完全依赖浏览器考试倒计时结束后浏览器可以发起自动交卷但服务端必须有兜底机制。原因包括考生电脑断网浏览器被关闭页面脚本停止运行客户端时间被修改自动交卷请求超时。因此考试结束时间必须以服务端时间为准。后台可以定时扫描已经超过截止时间、但仍处于作答状态的答卷并将其加入自动交卷任务。例如SELECT id FROM exam_attempt WHERE status ANSWERING AND exam_end_time NOW() LIMIT 500;扫描任务不应一次性处理全部过期答卷而应分批读取、分批提交避免后台任务自己制造新的流量峰值。九、以宏远培训考试系统为例集中交卷如何处理宏远培训考试系统主要面向企业、集团单位、政府机构和行业培训考核场景。这类考试通常具有人员集中、考试时间统一、成绩要求准确、过程需要留痕等特点。从宏远培训考试系统面向集中考试的设计思路来看解决最后阶段卡顿问题并不是单纯提高服务器配置而是将风险分散到考试全过程。1. 作答过程中持续保存考生答题时系统会持续记录作答内容减少最后交卷时一次性上传整张答卷的压力。即使考生在考试过程中出现页面刷新、短时断网或浏览器异常已经成功保存的答案仍然可以作为恢复基础。2. 交卷动作与答案保存分离考生点击交卷时系统重点确认当前答卷的最终状态而不是从零开始重新接收所有答题数据。这种设计可以缩短交卷接口的处理时间避免大量完整答卷同时上传。3. 防止重复提交针对考生重复点击交卷、网络重试和页面响应延迟等情况宏远培训考试系统会以考生考试记录作为唯一处理依据。同一次考试记录只能形成一个有效交卷结果。后续重复请求返回已有处理状态不重复生成成绩也不会重复增加交卷人数。4. 阅卷与统计分阶段处理客观题阅卷、成绩汇总、排名统计、部门对比和数据驾驶舱更新不必全部阻塞在考生交卷页面中。系统可以先确认答卷接收成功再完成成绩和统计处理减少集中交卷时的同步计算压力。5. 服务端自动交卷兜底对于考试时间结束后仍未主动交卷的人员系统根据服务端考试时间执行自动交卷避免完全依赖考生浏览器。同时自动交卷任务可以分批处理而不是在截止时间到达的一瞬间对所有人员同时执行完整阅卷。6. 交卷过程全程留痕系统会记录考生交卷时间、考试状态、答案保存情况和相关操作日志。在出现网络异常或考生对交卷结果存在疑问时管理员可以根据日志判断考生最后一次答案保存时间交卷请求是否到达服务器系统是否已经生成交卷凭证阅卷任务是否执行完成是否发生重复提交。对于正式考试来说这种可追溯能力与性能优化同样重要。十、服务器扩容仍然重要但不能替代架构设计削峰和幂等设计并不意味着服务器配置不重要。服务器仍然需要根据考试规模配置CPU核心数内存容量数据库连接数磁盘IOPS网络带宽应用实例数量消息队列消费者数量。但服务器扩容解决的是容量问题削峰和幂等解决的是流量模型和业务正确性问题。如果系统仍然采用“最后一次上传全部答案同步阅卷同步统计无幂等控制”的方式即使服务器配置提高一倍也可能只是在推迟故障出现的时间。十一、集中考试前应该怎样压测测试在线考试系统不能只测试登录人数。更重要的是模拟真实的最后5分钟。建议至少设计以下压测场景场景一持续自动保存模拟1000名考生每隔25至35秒随机保存部分答案。观察接口平均响应时间95%响应时间数据库写入速度连接池使用率错误率。场景二集中主动交卷在1分钟内逐步发起1000次交卷请求而不是平均分布在整场考试中。观察交卷接收成功率重复提交处理结果消息积压数量数据库锁等待线程池队列长度。场景三同一考生重复提交针对同一个attemptId并发发起5至10次交卷请求。正确结果应当是只生成一条交卷记录只生成一条成绩记录所有请求最终返回同一个交卷状态。场景四服务异常恢复在阅卷过程中模拟服务重启或消费者异常。系统恢复后应当能够继续处理未完成任务而不是出现答卷已经提交但永远没有成绩的情况。十二、上线验收不能只看“能不能交卷”一套在线考试系统是否稳定至少应检查以下内容答案是否持续自动保存页面刷新后答案能否恢复断网恢复后能否继续作答重复点击交卷是否只产生一个结果交卷接口超时后是否能够查询原结果考试结束后是否有服务端自动交卷阅卷任务失败后是否能够重试是否存在重复成绩是否存在交卷人数重复累计集中交卷时数据库是否出现长时间锁等待成绩统计是否与实际答卷一致交卷和异常过程是否有日志可追溯。只有登录、答题和单人交卷正常并不能证明系统可以支撑正式集中考试。十三、总结考试最后5分钟容易卡并不是因为考生突然变多而是因为大量考生在相近时间执行了自动保存、答案修改、主动交卷、自动交卷、阅卷和统计等高成本操作。真正有效的解决方案应当包括作答过程中持续保存答案只上传发生变化的数据交卷接口轻量化交卷与阅卷异步解耦使用消息队列削峰使用状态机控制答卷流程通过条件更新和唯一索引实现幂等避免交卷事务中更新热点统计数据由服务端完成超时自动交卷通过补偿任务处理异常状态。以宏远培训考试系统的集中考试设计思路为例稳定性并不依赖某一个技术点而是通过答案保存、交卷确认、重复提交控制、异步阅卷、自动交卷和日志追踪形成完整保障链路。对于企业正式考试来说系统不仅要保证“多数人能够交卷”更要保证每一名考生的答案都能保存、每一次交卷都能确认、每一条成绩都准确且可追溯。