招新平台是如何解决高并发判题问题的?
招新平台是如何解决高并发判题问题的?
在设计一个招新平台答题系统时,有一个典型需求:
同一道题提交人数越多,得分越低(衰减计分)。同时要保证高并发下的分数正确性。
一、业务场景 & 技术挑战
典型需求 包括:
- 用户高并发提交题解
- 只有 首次正确提交 才计入计数
- 答题人数越多,分数越低(例如对数衰减)
- 分数必须计算准确,不能出现并发错误
- 衰减后的分数必须可靠写入数据库
- 系统需要具备足够的高并发吞吐能力
二、整体实现思路
用户提交答案
↓
后端校验答案是否正确
↓
Redis Lua(原子):
SETNX 判断是否首次正确提交
INCR 计数
返回计数
↓
Java 计算衰减得分
↓
MySQL 落库:
- 用户得分(幂等写)
- 题目统计(乐观锁 + 重试)
三、Redis的Lua脚本实现计数原子性
-- KEYS[1] = 用户是否答对标记 key
-- KEYS[2] = 题目提交人数计数 key
-- 防重复:首次答对才能进入计分逻辑
local firstSolved = redis.call('SETNX', KEYS[1], 1)
if firstSolved == 0 then
return -1 -- -1 表示已提交过,不再得分
end
-- 原子计数
local count = redis.call('INCR', KEYS[2])
return count
这段代码执行了两个逻辑:
- 构造题目-用户key ,如果该key存在说明用户之前以及答对该题
- 如果是新用户答对,更新计数
四、Java 端:幂等写用户得分
INSERT INTO solve_record (user_id, question_id, degree)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE degree = VALUES(degree);
无则插入,有则修改
五、Java 端:更新题目(乐观锁 + 重试)
先用分数衰减算法计算出当前得分(略)
题目更新采用版本号法 实现:
UPDATE issue
SET solved_count = solved_count + 1,
version = version + 1
current_score = score
WHERE question_id = ?
AND version = ?;
如果版本不一致,说明出现并发冲突 ,需要重试:
public void updateQuestionSolvedCount(Long questionId) {
int maxRetry = 1; // 重试 1 次
for (int i = 0; i <= maxRetry; i++) {
// 1. 查询当前版本
Issue issue = IssueMapper.selectById(questionId);
int version = stat.getVersion();
// 2. 尝试更新
int rows = questionStatMapper.updateSolvedCount(questionId, version);
if (rows == 1) {
return; // 成功
}
// 3. 冲突 → 重试
if (i == maxRetry) {
throw new RuntimeException("Version conflict, retry failed");
}
// 休息一下避免忙等
try { Thread.sleep(10); } catch (Exception ignore) {}
}
}
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Aromatic!



