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-ForREMOTE_ADDR,若处于代理后面,NUM_PROXIES 的设置尤为重要。

3) 状态存储使用缓存

默认实现将计数存储在 Django 缓存后端。单进程/单服务器可用 LocMemCache,但在多工作进程/多实例环境下,Redis 等共享缓存几乎是必需的。


全局(Global)设置:最快速的起步



settings.py

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)

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)

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)

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

REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_CLASSES": [
        "rest_framework.throttling.ScopedRateThrottle",
    ],
    "DEFAULT_THROTTLE_RATES": {
        "login": "5/min",
        "uploads": "20/day",
        "search": "60/min",
    },
}

视图中仅声明 scope:

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() 即可自定义限制依据。

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 注册:

REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_CLASSES": [
        "path.to.LoginBurstThrottle",
    ],
    "DEFAULT_THROTTLE_RATES": {
        "login": "5/min",
    },
}

2) 更换缓存(cache attribute)

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 标识的霓虹招牌

  • nginx:在最前端拦截大量流量/攻击,起到防御屏障
  • DRF 节流:在应用层根据端点意义与成本,实施细粒度策略

尤其是“按视图特性差异化限制”,DRF 节流是最直观、最易维护的方案,且不受服务器环境变化影响。