DRF并发场景下限流机制生效不一致问题咨询
DRF限流阈值不准、并发下拦截数不稳定问题
根因
DRF内置限流(包括继承SimpleRateThrottle实现的自定义限流)的计数逻辑不是原子操作:
默认执行流程是「从缓存读取当前窗口请求数 → 判断是否超过阈值 → 写入当前请求时间戳更新计数」,三步操作之间没有加锁也没有做原子化封装。
当你用--workers 2 --threads=2启动Gunicorn时,服务端同时有4个并发执行单元处理请求,高并发下多个请求会同时读到未更新的旧计数值,全部判定为允许放行,最终导致实际通过的请求数远高于配置的10/s阈值,拦截数量随机无规律。
你压测时出现的拦截数4、6甚至0的情况,完全符合这个竞态问题的表现。
修复方案
- 替换默认非原子的限流逻辑,用Redis Lua脚本实现原子化限流校验和计数,从根源上避免并发竞态:
直接替换你原来的StatusApiThrottle实现即可:from rest_framework.throttling import SimpleRateThrottle import redis class StatusApiThrottle(SimpleRateThrottle): rate = '10/s' scope = 'status' # 复用现有Redis实例配置 _redis_cli = redis.Redis(host="127.0.0.1", port=6379, db=1) # 原子限流脚本:校验、计数、过期清理全流程在Redis侧单线程执行,无竞态 _throttle_lua = """ local cache_key = KEYS[1] local limit = tonumber(ARGV[1]) local cur_time = tonumber(ARGV[2]) local window_sec = tonumber(ARGV[3]) local cur_count = redis.call('ZCOUNT', cache_key, cur_time - window_sec, cur_time) if cur_count >= limit then return 0 end redis.call('ZADD', cache_key, cur_time, string.format('%d:%d', cur_time, math.random(1, 1000000))) redis.call('ZREMRANGEBYSCORE', cache_key, 0, cur_time - window_sec) redis.call('EXPIRE', cache_key, window_sec) return 1 """ def get_cache_key(self, request, view): return f"myapp:throttle:{self.scope}:{self.get_ident(request)}" def allow_request(self, request, view): if request.method == "POST": return True allowed = self._redis_cli.eval( self._throttle_lua, 1, self.get_cache_key(request, view), 10, self.timer(), 1 ) return bool(allowed) - 临时验证方法:如果需要快速确认根因,可以临时用单进程单线程启动服务
gunicorn app --workers 1 --threads 1,此时没有并发执行单元的竞态问题,限流结果会和预期完全一致,但该配置无法承载生产流量,仅用于问题验证。 - 注意:DRF内置限流仅适合低并发场景的粗略限流,只要是多进程/多线程部署+高并发请求,默认实现必然会出现阈值不准的问题,生产环境精准限流必须用原子化实现。
内容的提问来源于stack exchange,提问作者jebaseelan ravi
相关产品推荐
相关产品推荐

