django-ratelimit不够用因其限流维度单一、装饰器难以统一拦截各类视图,而中间件可早于权限校验介入,结合可配置策略(如路径前缀映射限流规则)、多维键名(ip+用户id等)及redis lua原子脚本,实现生产级精准限流。

为什么直接用 django-ratelimit 不够用
因为它的默认后端只支持内存或 Redis,但生产环境常需要按用户角色、API 分组、甚至 IP+Token 组合维度限流;django-ratelimit 的装饰器写法也难以统一拦截所有 API(比如 DRF 的 APIView 子类、函数视图、Schema 视图),中间件能更早介入请求生命周期,避免路由解析开销。
如何在中间件里做「可配置」的限流判断
关键不是硬编码规则,而是把限流策略抽成配置项,让每个路径/方法/用户类型走不同规则。示例中用字典映射路径前缀到限流参数:
RATE_LIMIT_CONFIG = {
r"^/api/v1/orders/": {"window": 60, "limit": 10, "key_func": "ip_and_user_id"},
r"^/api/v1/search/": {"window": 60, "limit": 30, "key_func": "ip_only"},
r"^/api/v1/admin/": {"window": 300, "limit": 5, "key_func": "token_header"},
}
中间件里用 re.match 匹配请求路径,查出对应规则;key_func 决定限流键名(如 "ip:192.168.1.100:user:123"),避免简单用 IP 导致登录用户被误限。
- 别用
request.path_info做精确匹配——它不含查询参数,但限流通常不关心 query,所以够用 -
window和limit必须存为整数,字符串会引发 Redis incr 操作失败 - 如果用 Redis 后端,键名建议加前缀如
f"rl:{key}",避免和其他业务 key 冲突
Redis 计数器怎么防并发超限
Django 中间件本身无状态,必须依赖外部存储计数。Redis 是最常用选择,但直接用 INCR + EXPIRE 有竞态风险:两个请求同时读到 0,都执行 INCR,结果变成 2,但只设了一次过期时间。
正确做法是用 Lua 脚本原子执行「增+判+设过期」:
lua_script = """
local current = redis.call("incr", KEYS[1])
if current == 1 then
redis.call("expire", KEYS[1], tonumber(ARGV[1]))
end
return current
"""
调用时传入 key 和 window 秒数:redis_client.eval(lua_script, 1, key, window)。返回值就是当前计数,超过 limit 就直接返回 429。
- 别用
cache.set(key, value, timeout)—— 它无法原子判断是否首次设置 - 如果 Redis 连接失败,建议 fallback 到放行(非阻断),否则整个服务可能雪崩
- 测试时可用
django.core.cache.cache的 dummy backend,但上线必须切到 Redis
DRF 和普通视图混用时如何统一拦截
中间件对所有请求一视同仁,但 DRF 的 APIView 可能已通过 permission_classes 拦截了未认证请求,而限流应在权限校验前触发(否则攻击者可反复用错误 token 触发鉴权逻辑)。所以中间件位置很重要:
确保它放在 MIDDLEWARE 列表中 SessionMiddleware 和 AuthenticationMiddleware 之后、但 CommonMiddleware 之前。这样能拿到 request.user(如果是已认证用户),又不会因权限拒绝而跳过限流。
- 对未登录用户,
request.user.is_authenticated为 False,此时应退化到 IP 或 header 提取的 token 字段 - 若接口允许匿名访问(如公开搜索),务必限制 IP 级别,否则容易被爬虫打穿
- 不要在中间件里调用
request.data或request.query_params—— DRF 的解析逻辑尚未执行,会触发异常
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











