基于DeepSeek的代码评测和自动纠错优化系统-Redis异步队列实现
用户提交代码后,我们需要运行用户代码,对每组测试数据进行评测;系统需要返回准确的运行结果、错误信息、输出对比等,于此同时还要保证整个过程,支持提交。在这一提交要求下,评测任务的强异步信号向传统的关系型数据库式存储提出挑战。
一,前言
用户提交代码后,我们需要运行用户代码,对每组测试数据进行评测;
系统需要返回准确的运行结果、错误信息、输出对比等,于此同时还要保证整个过程不影响主流程,支持高并发提交。
在这一提交要求下,评测任务的强异步信号向传统的关系型数据库式存储提出挑战
二,分析
关系型数据库作为队列有以下缺陷
1. 性能天花板过低
MySQL 表存储在SSD 磁盘上,读写吞吐量低,且通常面对带锁竞争,高延迟低瓶颈
2. 并发控制代价高昂
锁竞争:SELECT ... FOR UPDATE 导致行级锁,高并发时产生大量锁等待。
死锁风险:多个消费者同时抢锁时易触发死锁检测机制,消耗 CPU 资源。
连接池耗尽:长时间的事务占用数据库连接,导致 Too many connections 错误。
3. 数据生命周期不匹配
若选择将任务存储于关系型数据库,频繁的 INSERT/DELETE 导致页分裂和索引碎片
AUTO_INCREMENT 主键快速耗尽,需要定期执行 OPTIMIZE TABLE 维护表空间,并且需持续监控表碎片、死锁、长事务等问题
4. 可靠性降低
消费语义难保证:若消费者崩溃,status = 'processing' 的任务可能永远无法被重新处理(需额外实现超时回滚)
三,引入Redis队列实现异步队列

var redisQueueKey = "judge_queue"
utils.RDB.LPush(ctx, redisQueueKey, data)
for i := 0; i < n; i++ {
res, err := utils.RDB.BRPop(ctx, 0*time.Second, redisQueueKey).Result()
}
judge_queue定义 Redis 队列的键名
LPush 左端插入,Redis 会创建一个List类型的数据结构,将任务Push入队列中
BRPop 是 Redis 的阻塞式右端弹出操作,工作协程(Worker)通过此命令从队列获取任务。在 Redis 中形成一个先进先出(FIFO)的队列
BRPOP 是原子操作,确保多个消费者不会重复获取同一任务。
同时每个 Worker 独立运行,无需共享状态,天然避免竞态条件
package utils
import (
"github.com/redis/go-redis/v9"
)
var RDB *redis.Client
func InitRedis() {
RDB = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
DB: 0,
})
}
四,实现
生产者-消费者模式:通过 Redis 解耦任务提交与处理,提升系统吞吐量。
异步特性:用户提交代码后立即返回,评测结果通过数据库更新异步通知。
核心依赖:Redis 的高性能列表操作和 Go 的轻量级协程并发模型。
混合数据库架构设计
实现了性能与成本的平衡
热数据:将高频读写的数据(如任务队列)放在内存中,以 Redis 支撑高吞吐。
冷数据:将低频访问的持久化数据(如历史提交记录)存在磁盘,以SQLlite降低成本。
扩展性增强
横向扩展 Redis:通过集群分片应对队列数据增长。
读写分离 SQLlite:通过主从复制分离查询流量。
技术栈扬长避短
Redis 规避关系型瓶颈:
关系型数据库的锁机制和磁盘 IO 难以支撑高并发队列操作。
SQLlite 规避 NoSQL 弱点:
NoSQL 不擅长复杂查询(如「统计每题的提交次数」需要 MapReduce,而 SQL 一句搞定)。
更多推荐


所有评论(0)