限流,就是限制對(duì) API 的調(diào)用頻率。每一次 API 調(diào)用,都要花費(fèi)服務(wù)器的資源,因此很多 API 不會(huì)對(duì)用戶無(wú)限次地開放,請(qǐng)求達(dá)到某個(gè)次數(shù)后就不再允許訪問了,或者一段時(shí)間內(nèi),最多只允許訪問 API 指定次數(shù)。
django-rest-framework 為我們提供了 2 個(gè)常用的限流功能輔助類,分別是 AnonRateThrottle 和 - UserRateThrottle。
1 - AnonRateThrottle
AnonRateThrottle 用于限制未認(rèn)證用戶的訪問頻率,限制依據(jù)是用戶的 ip。
2 - UserRateThrottle
UserRateThrottle 用于限定認(rèn)證用戶,即網(wǎng)站的注冊(cè)用戶(目前我們博客不支持用戶登錄注冊(cè),所以這個(gè)類沒什么用)。兩個(gè)類可以用于同一 API,以便對(duì)不同類型的用戶實(shí)施不同的限流政策。
AnonRateThrottle 和 UserRateThrottle 這兩個(gè)輔助類限制頻率的指定格式為 "最大訪問次數(shù)/時(shí)間間隔",例如設(shè)置為 10/min,則只允許一分鐘內(nèi)最多調(diào)用接口 10 次。
超過限定次數(shù)的調(diào)用將拋出 exceptions.Throttled 異常,客戶端收到 429 狀態(tài)碼(too many requests)的響應(yīng)。
3 - 根據(jù)已有 API 列表和緩存情況來(lái)分析一下我們的限流政策:
接口名 URL 限流
文章列表 /api/posts/ 10/min
文章詳情 /api/posts/:id/ 10/min
分類列表 /categories/ 10/min
標(biāo)簽列表 /tags/ 10/min
歸檔日期列表 /posts/archive/dates/ 10/min
評(píng)論列表 /api/posts/:id/comments/ 10/min
文章搜索結(jié)果 /api/search/ 5/min
- (1) 首頁(yè)文章列表 API:有緩存,正常用戶不會(huì)訪問太頻繁,限定 10/min
- (2) 文章詳情 API:有緩存,正常用戶不會(huì)訪問太頻繁,限定 10/min
- (3) 分類、標(biāo)簽、歸檔日期列表,有緩存,正常用戶不會(huì)訪問太頻繁,限定 10/min
- (4) 評(píng)論列表,有緩存,正常用戶不會(huì)訪問太頻繁,限定 10/min
- (5) 搜索接口,正常用戶不會(huì)訪問太頻繁,限定 5/min
4 - 啟用限流有 2 種方式:一是全局設(shè)置,二是單個(gè)視圖設(shè)置;
單個(gè)視圖的設(shè)置會(huì)覆蓋全局設(shè)置。因?yàn)閹缀跛薪涌诙际菍?duì)匿名用戶限流,因此先來(lái)進(jìn)行全局設(shè)置。在項(xiàng)目配置文件 settings.py 中找到 REST_FRAMEWORK 配置項(xiàng),加入如下配置:
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': '10/min',
}
}
這樣,所有接口訪問頻率均被設(shè)置為 10/min。
對(duì)于搜索接口,我們制定的限流規(guī)則是 5/min,因此我們對(duì)這個(gè)視圖集的限流類進(jìn)行單獨(dú)設(shè)置。
因?yàn)槿峙渲弥校J(rèn)設(shè)置的限流頻率為 10/min,為了將限流類的默認(rèn)頻率設(shè)置為 5/min,我們需要繼承原限流類覆蓋它的 THROTTLE_RATES 屬性,代碼非常簡(jiǎn)單:
# 在views.py視圖函數(shù)里添加
from rest_framework.throttling import AnonRateThrottle
class PostSearchAnonRateThrottle(AnonRateThrottle):
THROTTLE_RATES = {"anon": "5/min"}
接著在搜索接口的視圖集中通過 throttle_classes 指定這個(gè)限流類:
class PostSearchView(HaystackViewSet):
index_models = [Post]
serializer_class = PostHaystackSerializer
throttle_classes = [PostSearchAnonRateThrottle]
5 - 現(xiàn)在來(lái)測(cè)試 10/min 訪問限制的接口,以文章列表接口 api/v1/posts/ 為例,在連續(xù)訪問 10 次后,接口返回了如下結(jié)果:
HTTP 429 Too Many Requests
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Retry-After: 52
Vary: Accept
{
"detail": "請(qǐng)求超過了限速。 Expected available in 52 seconds."
}
- 一分鐘后重新訪問又恢復(fù)了正常。
6 - 再來(lái)測(cè)試一下搜索接口,訪問 /api/v1/search/?text=markdown,在連續(xù)刷新 5 次后,接口返回如下結(jié)果:
HTTP 429 Too Many Requests
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Retry-After: 26
Vary: Accept
{
"detail": "請(qǐng)求超過了限速。 Expected available in 26 seconds."
}
- 一分鐘后重新訪問又恢復(fù)了正常。
?? 注意
如果搜索功能依賴 Elasticsearch 服務(wù),因此測(cè)試接口時(shí)需要運(yùn)行 Docker 容器,可參考 基于 drf-haystack 實(shí)現(xiàn)文章搜索接口。