以下文章来源于CodeSpirit-码灵,作者magiccodes
在考试系统中,当大量学生同时开始考试时,系统需要为每个学生创建考试记录(ExamRecord)和答题记录(ExamAnswerRecord)。传统的"按需创建"模式在高并发场景下存在以下问题:

考试记录预生成方案通过定时任务(每天凌晨1点)批量预生成所有已发布且尚未开始的考试的记录和答题记录,将数据库写入操作从"考试开始时刻"提前到"凌晨低负载时段",从而:
AttemptNumber = 1),后续考试动态创建CreateExamRecordAsync学生数据库缓存层定时预生成任务ExamSettingServiceExamSettingsController管理员CreateExamRecordAsync学生数据库缓存层定时预生成任务ExamSettingServiceExamSettingsController管理员阶段1:考试发布阶段2:定时预生成(每天凌晨1点)阶段3:学生开始考试alt[缓存命中][缓存未命中(新增学生)]阶段4:定时清理(每天凌晨2点)发布考试PublishExamSettingAsync更新考试状态为Published返回成功查询已发布且尚未开始的考试检查是否已预生成分批创建ExamRecord(NotStarted)批量创建ExamAnswerRecord写入预生成记录ID(过期时间=考试结束时间)打印详细日志点击开始考试查询预生成记录ID加载预生成记录UPDATE状态为InProgress设置StartTime快速启动(10-50ms) ✅动态创建完整记录常规启动(200-500ms) ⚠️查询已结束考试的NotStarted记录批量删除未使用记录清理相关缓存是否是否是否考试发布更新状态为Published等待定时任务执行定时任务每天凌晨1点查询已发布且尚未开始的考试是否已预生成?跳过该考试获取学生分组列表分批处理学生列表创建ExamRecordStatus=NotStarted创建ExamAnswerRecord列表写入缓存Key: exam:pregenerated:examId:studentId:1Value: recordIdExpire: 考试结束时间+1小时是否还有批次?预生成完成学生开始考试查询缓存缓存命中?加载预生成记录更新状态为InProgress设置StartTime快速启动 ✅动态创建记录常规启动 ⚠️定时清理任务每天凌晨2点查询已结束考试查找NotStarted记录批量删除清理缓存IExamRecordPreGenerationService)职责:
关键方法:
PreGenerateExamRecordsAsync(long examId) - 为指定考试预生成所有学生记录PreGenerateBatchAsync(long examId, IEnumerable<long> studentIds, int attemptNumber) - 批量预生成指定学生记录GetPreGeneratedRecordCacheKey(long examId, long studentId, int attemptNumber) - 生成缓存键ExamRecordService)职责:
关键方法:
CreateExamRecordAsync(long examId, long studentId, ...) - 创建或激活考试记录定时预生成任务处理器 (ExamRecordScheduledPreGenerationTaskHandler):
手动预生成任务处理器 (ExamRecordPreGenerationTaskHandler):
清理任务处理器 (ExamRecordCleanupTaskHandler):
新增 NotStarted = 0 状态,用于标识预生成的记录:
public enum ExamRecordStatus
{
NotStarted = 0, // 未开始(预生成状态)
InProgress = 1, // 进行中
Submitted = 2, // 已提交
Graded = 3 // 已批改
}预生成 → NotStarted
↓
开始考试 → InProgress
↓
提交考试 → Submitted
↓
批改完成 → Gradedexam:pregenerated:{examId}:{studentId}:{attemptNumber}示例:
exam:pregenerated:123:456:1核心原则:缓存过期时间 = 考试结束时间 + 1小时缓冲
设计理由:
BATCH_SIZE)DELAY_BETWEEN_BATCHES_MS)STOP_BEFORE_EXAM_START_MINUTES)示例日志:
⚠️ 距离考试开始时间不足 5 分钟,停止预生成。已处理: 800/1000,剩余: 200 名学生未处理NotStarted 状态的记录-- 默认查询(排除预生成记录)
SELECT * FROM ExamRecord
WHERE Status != 0 -- NotStarted
-- 显式查询预生成记录
SELECT * FROM ExamRecord
WHERE Status = 0 -- NotStarted指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
开始考试耗时 | 200-500ms | 10-50ms | 90%+ |
数据库写入压力 | 高峰期集中 | 分散到发布时 | 98% |
并发支持能力 | 500+ | 1000+ | 2倍 |
缓存命中率 | - | >85% | - |
AddRangeAsync)DeleteRangeAsync)Status = Published AND StartTime > 当前时间)AttemptNumber = 1)EndTime < 当前时间)NotStarted[INFO] ========================================
[INFO] 考试记录定时预生成任务开始执行
[INFO] ========================================
[INFO] 找到 3 个已发布且尚未开始的考试
[INFO] 考试 123 (数学期末考试) 已预生成,跳过
[INFO] 开始为考试 456 (英语期末考试) 预生成记录
[INFO] 获取到 1000 名学生需要预生成记录
[INFO] 开始分批预生成,每批 50 名学生,共 20 批,批次间延迟 200ms
[INFO] 第 1/20 批完成:成功 50,失败 0
...
[WARN] ⚠️ 距离考试开始时间不足 5 分钟,停止预生成。已处理: 800/1000,剩余: 200 名学生未处理
[INFO] 考试 456 预生成完成 - 成功: 798, 跳过: 200
[INFO] 定时预生成完成 - 总计: 3, 成功: 2, 跳过: 1, 失败: 0
[INFO] ========================================[INFO] ✅ 命中预生成记录,快速启动:考试ID=123, 学生ID=456, 记录ID=789
[WARN] ⚠️ 未命中预生成记录,执行动态创建:考试ID=123, 学生ID=999以下配置参数在 ExamRecordPreGenerationService.cs 中以常量形式定义:
参数 | 默认值 | 说明 |
|---|---|---|
BATCH_SIZE | 50 | 每批次处理的学生数量 |
DELAY_BETWEEN_BATCHES_MS | 200ms | 批次间延迟时间,避免系统压力过大 |
STOP_BEFORE_EXAM_START_MINUTES | 5分钟 | 开考前多久停止预生成,确保系统资源优先服务于考试 |
调整建议:
{
"ScheduledTasks":{
"Tasks":[
{
"Id":"exam-record-scheduled-pregeneration",
"Name":"考试记录定时预生成",
"Description":"每天凌晨1点为所有已发布且尚未开始的考试预生成记录",
"Type":"Cron",
"CronExpression":"0 0 1 * * *",
"HandlerType":"CodeSpirit.ExamApi.Tasks.ExamRecordScheduledPreGenerationTaskHandler",
"Timeout":"00:30:00",
"Enabled":true
},
{
"Id":"exam-record-cleanup",
"Name":"考试记录垃圾数据清理",
"Description":"清理未使用的预生成考试记录",
"HandlerType":"CodeSpirit.ExamApi.Tasks.ExamRecordCleanupTaskHandler",
"CronExpression":"0 0 2 * * *",
"Parameters":"{\"cleanupDays\": 7}",
"Enabled":true
}
]
}
}Cron表达式说明:
0 0 1 * * * 表示每天凌晨1点执行(预生成任务)0 0 2 * * * 表示每天凌晨2点执行(清理任务)NotStarted考试记录预生成方案通过提前创建和缓存优化两大核心策略,显著提升了系统在高并发场景下的性能和用户体验。方案设计充分考虑了容错、扩展性和可维护性,是一个生产级的优化方案。
📝 文档版本:v1.0 📅 最后更新:2025年12月 👤 维护团队:CodeSpirit 开发团队
本文分享自 CodeSpirit-码灵 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!