# DRF节流全攻略:为什么需要、如何设置、应用与自定义 在 nginx 反向代理中使用 `limit_req` 对入口进行“限流”无疑是有效的做法。但当你想为不同的视图/动作设置不同的策略(例如登录每分钟 5 次、上传每天 20 次、查询 API 每用户 1000 次)时,仅靠服务器/基础设施的配置往往难以满足需求。DRF 的节流功能让你可以在**应用层**根据“端点特性”创建对应的限制,成为必须掌握的基础技能。 --- ## 为什么需要节流 DRF 节流像权限(permission)一样决定是否允许请求,但区别在于它是**临时的**(请求频率限制)而非永久的。官方文档将节流描述为“控制客户端向 API 发送请求速率的临时状态”。 现实需求大致可归纳为: * **滥用/攻击缓解**:暴力破解登录、垃圾请求、爬虫、简单 DoS 等 * **成本/资源保护**:上传、外部 API 调用、重查询、生成式 AI 调用等“昂贵”端点 * **公平使用**:防止单个用户/密钥占用资源 * **策略编码**:将“每分钟 N 次”之类的规则放在代码中,而非基础设施 重要的是,节流既可以全局(global)也可以针对特定视图/动作(view/action)进行设置。仅靠 nginx 很难做到这一点。 --- ## DRF 节流的工作原理(核心概念) ### 1) Rate 字符串 DRF 通常使用类似 `"100/day"`、`"60/min"` 的字符串来定义限制。 ### 2) “谁”被限制(客户端识别) * `UserRateThrottle`:已认证用户按 **user id** 限制,未认证用户按 **IP** 限制 * `AnonRateThrottle`:仅对未认证(anonymous)请求按 IP 限制 * `ScopedRateThrottle`:按 **scope**(如 `uploads`)应用策略 IP 识别使用 `X-Forwarded-For` 或 `REMOTE_ADDR`,若处于代理后面,`NUM_PROXIES` 的设置尤为重要。 ### 3) 状态存储使用缓存 默认实现将计数存储在 Django 缓存后端。单进程/单服务器可用 `LocMemCache`,但在多工作进程/多实例环境下,Redis 等共享缓存几乎是必需的。 --- ## 全局(Global)设置:最快速的起步 `settings.py`: ```python REST_FRAMEWORK = { "DEFAULT_THROTTLE_CLASSES": [ "rest_framework.throttling.AnonRateThrottle", "rest_framework.throttling.UserRateThrottle", ], "DEFAULT_THROTTLE_RATES": { "anon": "100/day", "user": "1000/day", }, } ``` 这样即可为整个 API 应用默认策略。被限制的请求会得到 **HTTP 429(Too Many Requests)** 响应。 --- ## 如何在视图中应用:按端点差异化限制 ### 1) 基于类的视图(APIView) ```python from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.throttling import UserRateThrottle class ExpensiveView(APIView): throttle_classes = [UserRateThrottle] def get(self, request): return Response({"ok": True}) ``` ### 2) 基于函数的视图(@api_view) ```python from rest_framework.decorators import api_view, throttle_classes from rest_framework.throttling import UserRateThrottle from rest_framework.response import Response @api_view(["GET"]) @throttle_classes([UserRateThrottle]) def ping(request): return Response({"pong": True}) ``` ### 3) ViewSet 的特定 action(@action) ```python from rest_framework.decorators import action from rest_framework.throttling import UserRateThrottle from rest_framework.viewsets import ViewSet from rest_framework.response import Response class ItemViewSet(ViewSet): @action(detail=True, methods=["post"], throttle_classes=[UserRateThrottle]) def purchase(self, request, pk=None): return Response({"purchased": pk}) ``` action 级别的节流优先于 ViewSet 级别的设置。 --- ## 使用 ScopedRateThrottle 创建“按视图特性”策略(强烈推荐) Scoped 节流让你可以用有意义的名称(scope)区分策略,管理更清晰。 `settings.py`: ```python REST_FRAMEWORK = { "DEFAULT_THROTTLE_CLASSES": [ "rest_framework.throttling.ScopedRateThrottle", ], "DEFAULT_THROTTLE_RATES": { "login": "5/min", "uploads": "20/day", "search": "60/min", }, } ``` 视图中仅声明 scope: ```python from rest_framework.views import APIView from rest_framework.response import Response class LoginView(APIView): throttle_scope = "login" def post(self, request): return Response({"ok": True}) ``` DRF 会为带 `throttle_scope` 的视图使用 ScopedRateThrottle,并以 **scope + user id 或 IP** 生成唯一键进行计数。 --- ## 自定义节流:关键在于“如何获取键” 内置节流已能满足大多数需求,但实际业务中常见的自定义场景包括: * 登录按 **IP + username** 组合限制 * 按 API Key 限制 * 按特定头/租户/组织单位限制 * 使用非默认缓存(如 Redis 集群) ### 1) 最常见的方式:继承 SimpleRateThrottle 只需重写 `get_cache_key()` 即可自定义限制依据。 ```python from rest_framework.throttling import SimpleRateThrottle class LoginBurstThrottle(SimpleRateThrottle): scope = "login" def get_cache_key(self, request, view): username = (request.data.get("username") or "").lower().strip() ident = self.get_ident(request) # IP 基础识别 if not username: return None # 若无 username,则不限制 return f"throttle_login:{ident}:{username}" ``` 在 `settings.py` 注册: ```python REST_FRAMEWORK = { "DEFAULT_THROTTLE_CLASSES": [ "path.to.LoginBurstThrottle", ], "DEFAULT_THROTTLE_RATES": { "login": "5/min", }, } ``` ### 2) 更换缓存(cache attribute) ```python from django.core.cache import caches from rest_framework.throttling import AnonRateThrottle class CustomCacheAnonThrottle(AnonRateThrottle): cache = caches["alternate"] ``` --- ## 部署前必知 ### 1) 代理环境下 IP 识别一致性 IP 识别基于 `X-Forwarded-For`/`REMOTE_ADDR`。若代理层不正确配置 `NUM_PROXIES`,所有用户会被视为同一 IP。 ### 2) LocMemCache 在多进程/多服务器下不可靠 本地缓存会导致每个工作进程/服务器独立计数,节流失效。生产环境建议使用 Redis 等共享缓存。 ### 3) 并发竞争条件(race condition) DRF 内置实现可能在高并发下出现“多请求超额”的情况。若业务需要严格的 N 次限制(如支付/优惠券),建议使用原子计数(Redis INCR + EXPIRE)实现自定义节流。 ### 4) 客户端友好性:429 与 Retry-After DRF 默认返回 429。实现 `wait()` 方法即可在响应中加入 `Retry-After` 头,提示客户端何时可重试。 --- ## 结语:nginx 与 DRF 节流,两者兼顾 ![机器人俱乐部的入场限制与 429 标识的霓虹招牌](/media/editor_temp/6/c96bb1d6-7cde-48a0-98aa-07396570391d.png) * **nginx**:在最前端拦截大量流量/攻击,起到防御屏障 * **DRF 节流**:在应用层根据端点意义与成本,实施细粒度策略 尤其是“按视图特性差异化限制”,DRF 节流是最直观、最易维护的方案,且不受服务器环境变化影响。