接口限流:限制接口的訪問(wèn)頻率
限流,顧名思義,就是限制對(duì) API 的調(diào)用頻率。每一次 API 調(diào)用,都要花費(fèi)服務(wù)器的資源,因此很多 API 不會(huì)對(duì)用戶無(wú)限次地開放,請(qǐng)求達(dá)到某個(gè)次數(shù)后就不再允許訪問(wèn)了,或者一段時(shí)間內(nèi),最多只允許訪問(wèn) API 指定次數(shù)。
目前,我們的接口是沒(méi)有任何限流措施的,只要用戶調(diào)用接口,服務(wù)器就會(huì)處理并返回?cái)?shù)據(jù)。為了防止接口被惡意用戶刷爆,我們來(lái)給接口限流。
上一篇中我們已經(jīng)整理了接口并加入了緩存,我們的限流政策可以根據(jù)緩存的設(shè)置情況來(lái)制定。對(duì)于緩存時(shí)間較長(zhǎng)的接口,可以適當(dāng)放寬限制,而對(duì)于可能需要訪問(wèn)數(shù)據(jù)庫(kù)的接口,則進(jìn)行嚴(yán)格的限制。
django-rest-framework 為我們提供了 2 個(gè)常用的限流功能輔助類,分別是 AnonRateThrottle 和 UserRateThrottle。AnonRateThrottle ?用于限制未認(rèn)證用戶的訪問(wèn)頻率,限制依據(jù)是用戶的 ip。UserRateThrottle 用于限定認(rèn)證用戶,即網(wǎng)站的注冊(cè)用戶(目前我們博客不支持用戶登錄注冊(cè),所以這個(gè)類沒(méi)什么用)。兩個(gè)類可以用于同一 API,以便對(duì)不同類型的用戶實(shí)施不同的限流政策。
這兩個(gè)輔助類限制頻率的指定格式為 "最大訪問(wèn)次數(shù)/時(shí)間間隔",例如設(shè)置為 10/min,則只允許一分鐘內(nèi)最多調(diào)用接口 10 次。超過(guò)限定次數(shù)的調(diào)用將拋出 exceptions.Throttled 異常,客戶端收到 429 狀態(tài)碼(too many requests)的響應(yīng)。
再次根據(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 |
補(bǔ)充說(shuō)明:
首頁(yè)文章列表 API:有緩存,正常用戶不會(huì)訪問(wèn)太頻繁,限定 10/min 文章詳情 API:有緩存,正常用戶不會(huì)訪問(wèn)太頻繁,限定 10/min 分類、標(biāo)簽、歸檔日期列表,有緩存,正常用戶不會(huì)訪問(wèn)太頻繁,限定 10/min 評(píng)論列表,有緩存,正常用戶不會(huì)訪問(wèn)太頻繁,限定 10/min 搜索接口,正常用戶不會(huì)訪問(wèn)太頻繁,限定 5/min
接口限流規(guī)則制定好后,接下來(lái)就設(shè)置限流輔助類就可以了。
啟用限流有 2 種方式,一是全局設(shè)置,二是單個(gè)視圖設(shè)置,單個(gè)視圖的設(shè)置會(huì)覆蓋全局設(shè)置。因?yàn)閹缀跛薪涌诙际菍?duì)匿名用戶限流,因此先來(lái)進(jìn)行全局設(shè)置。在項(xiàng)目配置文件 common.py 中找到 REST_FRAMEWORK 配置項(xiàng),加入如下配置:
#?filename="common.py"
REST_FRAMEWORK?=?{
????'DEFAULT_THROTTLE_CLASSES':?[
????????'rest_framework.throttling.AnonRateThrottle',
????],
????'DEFAULT_THROTTLE_RATES':?{
????????'anon':?'10/min',
????}
}
這樣,所有接口訪問(wèn)頻率均被設(shè)置為 10/min。
對(duì)于搜索接口,我們制定的限流規(guī)則是 5/min,因此我們對(duì)這個(gè)視圖集的限流類進(jìn)行單獨(dú)設(shè)置。
因?yàn)槿峙渲弥?,默認(rèn)設(shè)置的限流頻率為 10/min,為了將限流類的默認(rèn)頻率設(shè)置為 5/min,我們需要繼承原限流類覆蓋它的 THROTTLE_RATES 屬性,代碼非常簡(jiǎn)單:
#?filename="blog/views.py"
from?rest_framework.throttling?import?AnonRateThrottle
class?PostSearchAnonRateThrottle(AnonRateThrottle):
????THROTTLE_RATES?=?{"anon":?"5/min"}
接著在搜索接口的視圖集中通過(guò) throttle_classes 指定這個(gè)限流類:
#?filename="blog/views.py"
class?PostSearchView(HaystackViewSet):
????index_models?=?[Post]
????serializer_class?=?PostHaystackSerializer
????throttle_classes?=?[PostSearchAnonRateThrottle]
我們來(lái)測(cè)試一下,限流是否真的起了作用。
首先來(lái)測(cè)試 10/min 訪問(wèn)限制的接口,以文章列表接口 api/v1/posts/ 為例,在連續(xù)訪問(wèn) 10 次后,接口返回了如下結(jié)果:
HTTP 429 Too Many Requests Allow: GET, HEAD, OPTIONS Content-Type: application/json Retry-After: 52 Vary: Accept
{ "detail": "請(qǐng)求超過(guò)了限速。Expected available in 52 seconds." }
一分鐘后重新訪問(wèn)又恢復(fù)了正常。
再來(lái)測(cè)試一下搜索接口,訪問(wèn) /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)求超過(guò)了限速。Expected available in 26 seconds." }
一分鐘后重新訪問(wèn)又恢復(fù)了正常。
!!! note "注意"
因?yàn)樗阉鞴δ芤蕾?Elasticsearch 服務(wù),因此測(cè)試接口時(shí)需要運(yùn)行 Docker 容器,可參考《基于 drf-haystack 實(shí)現(xiàn)文章搜索接口》這篇文章。
https://www.zmrenwu.com/courses/django-rest-framework-tutorial/materials/101/
參考資料
HelloGitHub-追夢(mèng)人物:?https://www.zmrenwu.com

