手摸手,带你用Django REST Framework撸接口系列八(限流篇)

类似于[权限],限流器决定是否应当授权请求。限流器指示临时状态,并被用于控制客户端能够向API发出请求的频率。

限流(Throttle)就是限制客户端对API 的调用频率,是API开发者必须要考虑的因素。比如个别客户端(比如爬虫程序)短时间发起大量请求,超过了服务器能够处理的能力,将会影响其它用户的正常使用。又或者某个接口占用数据库资源比较多,如果同一时间该接口被大量调用,服务器可能会陷入僵死状态。为了保证API服务的稳定性,并防止接口受到恶意用户的攻击,我们必须要对我们的API服务进行限流。

DRF中限制对API的调用频率非常简便,它为我们主要提供了3个可插拔使用的限流类,分别是AnonRateThrottle, UserRateThrottleScopeRateThrottle类。

  • AnonRateThrottle 用于限制未认证用户的请求频率,主要根据用户的 IP地址来确定用户身份。
  • UserRateThrottle 用于限定认证用户的请求频率,可对不同类型的用户实施不同的限流政策。
  • ScopeRateThrottle可用于限制对 API 特定部分的访问。只有当正在访问的视图包含 throttle_scope 属性时才会应用此限制。这个与UserRateThrottle类的区别在于一个针对用户限流,一个针对API接口限流。

DRF限制频率的指定格式为 “最大访问次数/时间间隔”,例如设置为 5/min,则只允许一分钟内最多调用接口 5 次。其它常用格式包括”10/s”, “100/d”等。超过限定次数的调用将抛出 exceptions.Throttled 异常,客户端收到 429 状态码(too many requests)的响应。

全局限流

通过使用 DEFAULT_THROTTLE_CLASSESDEFAULT_THROTTLE_RATES 默认限流策略将被全局设定,例如:

1
2
3
4
5
6
7
8
9
10
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': (
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
),
'DEFAULT_THROTTLE_RATES': {
'anon': '100/day',
'user': '1000/day'
}
}

被用于 DEFAULT_THROTTLE_RATES 中的流量描述可能包括 second, minute, hour or day 作为限流周期

局部限流

对于基于 APIView 类的视图,您可以以每个视图或每个视图集为基础设置限流策略

1
2
3
4
5
6
7
8
9
10
11
12
from rest_framework.response import Response
from rest_framework.throttling import UserRateThrottle
from rest_framework.views import APIView

class ExampleView(APIView):
throttle_classes = (UserRateThrottle,)

def get(self, request, format=None):
content = {
'status': 'request was permitted'
}
return Response(content)

或者,如果您使用带有 @api_view 装饰器的基于函数的视图

1
2
3
4
5
6
7
@api_view(['GET'])
@throttle_classes([UserRateThrottle])
def example_view(request, format=None):
content = {
'status': 'request was permitted'
}
return Response(content)

视图类或视图集中使用限流类

DRF中还可以在单个视图或单个视图集中进行限流配置,单个视图中的配置会覆盖全局设置。现在我们希望保留settings.py的限流全局配置,并专门为文章资源列表/v1/articles定制一个限流类,新的访问频率限制为匿名用户为”5/min”, 认证用户为”30/min”,该配置仅对文章资源列表这个接口生效。

我们首先在app文件夹blog目录下新建throttles.py, 添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
from rest_framework.throttling import AnonRateThrottle, UserRateThrottle


class ArticleAnonRateThrottle(AnonRateThrottle):
"""未登录用户文章视图限流类"""
THROTTLE_RATES = {"anon": "5/min"}


class ArticleUserRateThrottle(UserRateThrottle):
"""登录用户文章视图先流泪"""
THROTTLE_RATES = {"user": "30/min"}

我们通过继承自定义了ArticleAnonRateThrottle, ArticleUserRateThrottle两个类,并通过THROTTLE_RATES属性设置了新的访问频率限制。现在我们可以将它们应用到views.py中对应文章资源列表的API视图类。无需重启测试服务器,你将发现新的限流设置已经生效了。

1
2
3
4
5
6
class ArticleView(viewsets.ModelViewSet):
"""文章列表视图类"""
queryset = Article.objects.all()
serializer_class = ArticleSerializer
# 视图限流
throttle_classes = (ArticleAnonRateThrottle, ArticleUserRateThrottle)

有时对一个认证用户进行限流不仅要限制每分钟的请求次数,还需要限制每小时的请求次数,这时该如何操作呢? 我们可以自定义两个UserRateThrottle子类,并设置不同的scope,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from rest_framework.throttling import UserRateThrottle


class MinuteUserRateThrottle(UserRateThrottle):
"""登录用户每分钟限流"""
# 当前限流器配置的限流速率流参数名,如:THROTTLE_RATES = {"user": "10/min"}
scope = "limit_user_minute"
# 设置限流速率,也可直接在settings中配置
THROTTLE_RATES = {"limit_user_minute": "5/min"}


class HourUserRateThrottle(UserRateThrottle):
"""登录用户每分钟限流"""
# 当前限流器配置的限流速率流参数名,如:THROTTLE_RATES = {"user": "10/min"}
scope = "limit_user_hour"
# 设置限流速率,也可直接在settings中配置
THROTTLE_RATES = {"limit_user_hour": "10/hour"}

视图类中换上我们自定义的限流类

1
throttle_classes = (MinuteUserRateThrottle, HourUserRateThrottle)

此时第一分钟内我们可以访问5次,超过5次会被限流,一分钟过后可以继续访问,一小时内访问次数超过10次,又会被限流。

ScopeRateThrottle类

AnonRateThrottle和UserRateThrottle类都是针对单个用户请求进行限流的,而ScopeRateThrottle类是针对不同API接口资源进行限流的,限制的是所有用户对接口的访问总数之和。使用时直接在视图类里通过throttle_scope 属性指定限流范围(scope), 然后在settings.py对不同scope设置限流频率。例子如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ArticleView(viewsets.ModelViewSet):
"""文章列表视图类"""
queryset = Article.objects.all()
serializer_class = ArticleSerializer
# 设定限流范围参数名
throttle_scope = "article_view"
def perform_create(self, serializer):
"""添加文章时绑定当前登录用户为文章的作者"""
# 将request.user与author绑定。调用create方法时执行如下函数。
serializer.save(author=self.request.user)


class UserView(viewsets.ModelViewSet):
"""文章列表视图类"""
queryset = User.objects.all()
serializer_class = UserSerializer
# 设定限流范围参数名
throttle_scope = "user_view"

针对不同api接口设置不同限流频率。如下配置代表文章资源每分钟限10次请求(所有用户访问数量之和),用户资源接口限每分钟5次。

1
2
3
4
5
6
7
8
9
10
11
12
13
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle',
'rest_framework.throttling.ScopedRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': '2/min',
'user': '10/min',
'article_view': '10/min',
'user_view': '5/min',
}
}

自定义限流类

有时你还需要自定义限流类。这时你需要继承BaseThrottle类、SimpleRateThrottle或者UserRateThrottle类,然后重写allow_request(self, request, view)或者get_rate(self, request=none)方法。DRF给的示例方法如下所示,该限流类10个请求中只允许一个通过。

若要创建自定义限流器,请重写 BaseThrottle 并使用 .allow_request(self, request, view)。如果允许请求,则该方法应返回 True ,否则返回 False

你也可以选择性地重写 .wait()方法。如果被使用,.wait() 应返回尝试下一个请求前建议的等待秒数,或者 None。只有在 .allow_request() 先前已返回 False 时才会调用 .wait()方法。

如果使用了 .wait() 方法并限制了请求,则响应中将包含 Retry-After 头。

以下例子为每10个请求中随机限制一个请求

1
2
3
4
5
import random

class RandomRateThrottle(throttling.BaseThrottle):
def allow_request(self, request, view):
return random.randint(1, 10) != 1