# DRF Throttling 中使用 scope 的两种方式比较:ScopedRateThrottle 与 UserRateThrottle 继承 在 DRF 中实现基于 scope 的限流时,常见的两种模式如下。 * **方式 A**:继承 `UserRateThrottle`(或 `SimpleRateThrottle`),创建一个带有固定 scope 的自定义类,并直接在视图中通过 `throttle_classes` 指定。 * **方式 B**:在 `DEFAULT_THROTTLE_CLASSES` 中注册 `ScopedRateThrottle`,然后在视图中仅设置 `throttle_scope = "login"`。 先回答“最终效果是否相同?”的问题: > **当你只想按“用户 ID(或未登录 IP)+ scope”来限制速率**时,**两种方式的结果几乎相同**。区别主要体现在: > - **实现方式(防止错误)** > - **可扩展性(多策略/自定义基准)** > - **代码结构(设置中心 vs 代码中心)** --- ## 共同点:两者都使用 “scope + (user id 或 IP)” 生成键 * `ScopedRateThrottle` 在视图中存在 `throttle_scope` 时,会以 **scope + 用户 ID / IP** 组合生成键,并在 `DEFAULT_THROTTLE_RATES` 中查找对应的速率。 * `UserRateThrottle` 默认按 **已认证用户的 user id** 或 **未认证用户的 IP** 限制;若需要多种策略,官方示例建议通过继承并为每个类设置不同的 scope。 --- ## 差异 1:scope 的位置——视图属性 vs 类属性 ### 方式 A:在类中固定 scope(显式) ```python from rest_framework.throttling import UserRateThrottle from rest_framework.views import APIView class CustomDomainSaveThrottle(UserRateThrottle): scope = "custom_domain_save" class SaveCustomDomainView(APIView): throttle_classes = [CustomDomainSaveThrottle] ``` * scope 固定在类中。 * 视图只需声明使用该类即可。 * 减少了在每个视图中手动输入 scope 字符串时的拼写错误。 ### 方式 B:在视图中仅设置 scope,使用共享 throttle(设置中心) ```python REST_FRAMEWORK = { "DEFAULT_THROTTLE_CLASSES": [ "rest_framework.throttling.ScopedRateThrottle", ], "DEFAULT_THROTTLE_RATES": { "login": "5/min", } } from rest_framework.views import APIView class LoginView(APIView): throttle_scope = "login" ``` * scope 直接写在视图属性中。 * 共享的 `ScopedRateThrottle` 会根据视图的 `throttle_scope` 在设置中查找速率。 * 代码更简洁,所有策略集中在 settings 中管理。 --- ## 差异 2:谁来保证应用——全局注册 vs 视图挂载 ### 方式 B 的关键点(常被误解) 即使在 `DEFAULT_THROTTLE_CLASSES` 中加入 `ScopedRateThrottle`,**没有 `throttle_scope` 的视图也不会被限流**。因此,担心全局注册会导致所有 API 都被限制的情况通常不必担心。 ### 方式 A 的优势 不需要改动全局设置即可**仅在特定视图上挂载**所需的限流策略。适用于团队或服务结构中无法频繁修改 settings,或只想对某些端点强制限流的场景。 --- ## 差异 3:可扩展性——单一限制之外的场景更适合方式 A 两种方式在“单一视图、单一 scope、单一速率”时表现相似。随后差异显现。 ### 1) 在同一视图上使用多种限流(如 burst + sustained) DRF 文档示例展示了同时使用多个 `UserRateThrottle` 的模式: ```python class Burst(UserRateThrottle): scope = "burst" class Sustained(UserRateThrottle): scope = "sustained" class SearchView(APIView): throttle_classes = [Burst, Sustained] ``` 仅靠 `ScopedRateThrottle` 难以自然表达“同一视图拥有两个 scope”,因此需要自定义类或实现。 ### 2) 限制基准从 user/IP 变更 `ScopedRateThrottle` 默认按 **user id / IP** 计算键。实际业务中常见需求: * 登录:按 **IP + username 组合** 限制(防止账号定向攻击) * 组织/租户:按 **tenant_id** 限制 * API Key:按 **key** 限制 这些情况需要自定义 `get_cache_key()` 等逻辑,天然倾向于方式 A(继承/自定义类)。 --- ![API 连接的两把钥匙](/media/editor_temp/6/e98f026a-a38e-43ca-8c86-fa7966dd3cdf.png) ## 那么应该选哪种? ### 大多数“按视图限流”场景 → 方式 B(ScopedRateThrottle) * 代码最小化 * 在 settings 中统一管理策略 * 视图仅需声明 `throttle_scope` * 没有 `throttle_scope` 的视图不受限流 ### 以下任一情况 → 方式 A(继承/自定义类) * 同一视图需要 **多重限流**(burst + sustained 等) * 限制基准不是 **user/IP**(如 username+IP、tenant、API key 等) * 需要避免全局设置变更,只在特定视图强制限流 * 想通过类名将策略“文档化”,让阅读者一眼看出意图 **相关文章**: - [DRF Throttling 完全攻略:设置、应用、定制指南](/ko/whitedec/2026/1/26/drf-throttling-complete-guide/)