限流设计:保护系统的第一道防线
为了防止用户频繁提交、滥用评测资源,智评 Code+ 实现了一套基于 Redis 的用户提交限流中间件。它作为中间件插入每次提交请求中,在高并发场景下起到了良好的流量控制作用。
为什么需要限流?
-
保护评测资源:每次代码提交会触发 Docker 评测,成本较高;
-
防刷防误触:避免用户频繁提交影响他人评测任务;
-
保障系统稳定性:防止 Redis 队列和数据库在短时间内被刷爆。
实现方式:Redis 计数 + 过期时间窗口
核心思路:每个用户每隔 N 秒最多允许提交 M 次代码。
我使用了 Redis 的 key-value 机制实现滑动窗口限流:
key := fmt.Sprintf("rate_limit:user:%d", userID)
每次提交:
-
读取当前提交次数(通过 Redis 的 GET);
-
如果 key 不存在,则初始化为 1 并设置过期时间;
-
如果存在且未超限,则自增一次;
-
如果超出限制,则直接 Abort 拦截请求。
代码实现要点
func RateLimitMiddleware(maxReq int, duration time.Duration) gin.HandlerFunc {
return func(c *gin.Context) {
// 获取用户 ID
val, exists := c.Get("user_id")
...
key := fmt.Sprintf("rate_limit:user:%d", userID)
// 读取提交次数
countStr, err := utils.RDB.Get(ctx, key).Result()
if err == redis.Nil {
// 第一次提交,初始化 key
_ = utils.RDB.Set(ctx, key, 1, duration).Err()
} else if count >= maxReq {
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{"error": "提交过于频繁"})
return
} else {
_ = utils.RDB.Incr(ctx, key).Err()
}
c.Next()
}
}
注意几个细节:
-
Redis 设置了 过期时间,窗口自动滑动;
-
支持配置参数:每 N 秒 M 次,灵活调节;
-
错误处理逻辑较为稳健,避免因 Redis 错误导致服务异常。
实战应用场景
比如:每个用户 10 秒内最多提交 3 次。
在 Gin 中使用非常简单:
r.POST("/auth/problems/:id/submit", RateLimitMiddleware(3, 10*time.Second), SubmitCode)
只需一句话,就能为所有敏感路由提供限流保护。
后续可扩展点
拓展点 | 思路 |
---|---|
精细限流策略 | 可针对不同 API 设置不同限流规则 |
IP + User 双维度 | 限制用户行为的同时防止匿名滥用 |
滑动窗口算法 | 可引入 token bucket / leaky bucket 算法实现更平滑的流控 |
限流监控 | 将被限流日志接入 Prometheus / ELK 方便运维观测 |
总结
限流中间件虽然是一个小组件,但在系统高可用性设计中起着至关重要的作用。它为整个智评平台建立了一道流量防火墙,确保了服务稳定与公平使用。、