Django REST framework 限流功能的使用
正文開始
先說一個(gè)限流這個(gè)概念,最早接觸這個(gè)概念是在前端。真實(shí)的業(yè)務(wù)場景是在搜索框中輸入文字進(jìn)行搜索時(shí),并不希望每輸一個(gè)字符都去調(diào)用后端接口,而是有停頓后才真正的調(diào)用接口。這個(gè)功能很有必要,一方面減少前端請求與渲染的壓力,同時(shí)減輕后端接口訪問的壓力。類似前端的功能的代碼如下:
// 前端函數(shù)限流示例
function throttle(fn, delay) {
var timer;
return function () {
var _this = this;
var args = arguments;
if (timer) {
return;
}
timer = setTimeout(function () {
fn.apply(_this, args);
timer = null;
}, delay)
}
}
但是后端的限流從目的上來說與前端類似,但是實(shí)現(xiàn)上會(huì)有所不同,讓我們看看 DRF 的限流。
1. DRF 中的限流
項(xiàng)目配置
# demo/settings.py
REST_FRAMEWORK = {
# ...
'DEFAULT_THROTTLE_CLASSES': (
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle',
'rest_framework.throttling.ScopedRateThrottle',
),
'DEFAULT_THROTTLE_RATES': {
'anon': '10/day',
'user': '2/day'
},
}
# article/views.py
# 基于ViewSet的限流
class ArticleViewSet(viewsets.ModelViewSet, ExceptionMixin):
"""
允許用戶查看或編輯的API路徑。
"""
queryset = Article.objects.all()
# 使用默認(rèn)的用戶限流
throttle_classes = (UserRateThrottle,)
serializer_class = ArticleSerializer
# 基于view的限流
@throttle_classes([UserRateThrottle])
因?yàn)槲遗渲玫挠脩裘刻熘荒苷埱髢纱?,所以在請求第三次之后就?huì)給出 429 Too Many Requests的異常,具體的異常信息為下一次可用時(shí)間為 86398 秒后。
2. 限流進(jìn)階配置
上述演示的限流配置適用于對用戶的限流,比如我換個(gè)用戶繼續(xù)訪問,依然是有兩次的機(jī)會(huì)。
$ curl -H 'Accept: application/json; indent=4' -u root:root http://127.0.0.1:8000/api/article/1/
{
"id": 1,
"creator": "admin",
"tag": "現(xiàn)代詩",
"title": "如果",
"content": "今生今世 永不再將你想起\n除了\n除了在有些個(gè)\n因落淚而濕潤的夜里 如果\n如果你愿意"
}
分別介紹一下三種限流類
- AnonRateThrottle 適用于任何用戶對接口訪問的限制
- UserRateThrottle 適用于請求認(rèn)證結(jié)束后對接口訪問的限制
- ScopedRateThrottle 適用于對多個(gè)接口訪問的限制
所以三種不同的類適用于不同的業(yè)務(wù)場景,具體使用根據(jù)不同的業(yè)務(wù)場景選擇,通過配置相對應(yīng) scope 的頻率的配置就可以達(dá)到預(yù)期的效果。
3. 限流思路分析
試想一下如果是你編碼實(shí)現(xiàn)這個(gè)需求應(yīng)該怎么實(shí)現(xiàn)?
其實(shí)這個(gè)功能不難,核心的參數(shù)就是 時(shí)間、次數(shù)、使用范圍,下面演示對函數(shù)調(diào)用次數(shù)的限制。
from functools import wraps
TOTAL_RATE = 2
FUNC_SCOPE = ['test', 'test1']
def rate_count(func):
func_num = {
# 需要注意函數(shù)名不能重復(fù)
func.__name__: 0
}
@wraps(func)
def wrapper():
if func.__name__ in FUNC_SCOPE:
if func_num[func.__name__] >= TOTAL_RATE:
raise Exception(f"{func.__name__}函數(shù)調(diào)用超過設(shè)定次數(shù)")
result = func()
func_num[func.__name__] += 1
print(f" 函數(shù) {func.__name__} 調(diào)用次數(shù)為: {func_num[func.__name__]}")
return result
else:
# 不在計(jì)數(shù)限制的函數(shù)不受限制
return func()
return wrapper
@rate_count
def test1():
pass
@rate_count
def test2():
print("test2")
pass
if __name__ == "__main__":
try:
test2()
test2()
test1()
test1()
test1()
except Exception as e:
print(e)
test2()
test2()
"""
test2
test2
函數(shù) test1 調(diào)用次數(shù)為: 1
函數(shù) test1 調(diào)用次數(shù)為: 2
test1函數(shù)調(diào)用超過設(shè)定次數(shù)
test2
test2
"""
這里實(shí)現(xiàn)了對函數(shù)調(diào)用次數(shù)的監(jiān)控同時(shí)設(shè)置了能夠使用該功能的函數(shù)。當(dāng)函數(shù)調(diào)用次數(shù)超過設(shè)定閥值久拋出異常。只是這里沒有對時(shí)間做限制。
4. 源碼分析
剛才分析了如何實(shí)現(xiàn)對函數(shù)調(diào)用次數(shù)的限制,對于一個(gè)請求來說可能會(huì)復(fù)雜一點(diǎn),下面就看看 DRF 如何實(shí)現(xiàn)的:
class SimpleRateThrottle(BaseThrottle):
# ......
def allow_request(self, request, view):
"""
Implement the check to see if the request should be throttled.
On success calls `throttle_success`.
On failure calls `throttle_failure`.
"""
if self.rate is None:
return True
self.key = self.get_cache_key(request, view)
if self.key is None:
return True
self.history = self.cache.get(self.key, [])
self.now = self.timer()
# 根據(jù)設(shè)置時(shí)間的限制改變請求次數(shù)的緩存
while self.history and self.history[-1] <= self.now - self.duration:
self.history.pop()
# 核心邏輯就是這里判斷請求次數(shù)
if len(self.history) >= self.num_requests:
return self.throttle_failure()
return self.throttle_success()
# ......
class UserRateThrottle(SimpleRateThrottle):
"""
Limits the rate of API calls that may be made by a given user.
The user id will be used as a unique cache key if the user is
authenticated. For anonymous requests, the IP address of the request will
be used.
"""
scope = 'user'
def get_cache_key(self, request, view):
if request.user.is_authenticated:
ident = request.user.pk
else:
# 考慮到用戶沒有認(rèn)證的情況 與 AnonRateThrottle 中 key 一致
ident = self.get_ident(request)
# 根據(jù)設(shè)置的范圍構(gòu)建緩存的 key
return self.cache_format % {
'scope': self.scope,
'ident': ident
}
綜上所述:
- 核心的判斷邏輯依舊是緩存中獲取每個(gè)用戶調(diào)用次數(shù),根據(jù)范圍與時(shí)間判斷是否超過設(shè)置定的閥值。
- 不同類型的限流,在緩存 key 的設(shè)計(jì)上會(huì)有區(qū)別,默認(rèn)的 key 為請求中REMOTE_ADDR。
5. 其它注意事項(xiàng)
- 因?yàn)檫@里的實(shí)現(xiàn)用到緩存,所以需要注意在多實(shí)例部署的情況下需要配置統(tǒng)一的緩存服務(wù)(默認(rèn)的緩存為 Django 基于內(nèi)存實(shí)現(xiàn)的)。
- 緩存服務(wù)的重啟可能會(huì)導(dǎo)致已有的計(jì)數(shù)清零,如果有較強(qiáng)的業(yè)務(wù)邏輯需要,還請自己實(shí)現(xiàn)限流的邏輯。
- 如果是自定義的用戶表,需要重寫緩存中 get_cache_key 的邏輯。
- 如果需要統(tǒng)計(jì)分析用戶被限流情況也是需要重新設(shè)計(jì)限流的邏輯。
- 限流的邏輯在生產(chǎn)環(huán)境中慎用,因?yàn)闀?huì)限制用戶使用產(chǎn)品,對用戶不夠友好。
參考資料
以上就是Django REST framework 限流功能的使用的詳細(xì)內(nèi)容,更多關(guān)于Django REST framework 限流功能的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python中比較特別的除法運(yùn)算和冪運(yùn)算介紹
這篇文章主要介紹了Python中比較特別的除法運(yùn)算和冪運(yùn)算介紹,“/”這個(gè)是除法運(yùn)算,那么這個(gè)“//”呢?“*”這個(gè)是乘法運(yùn)算,那么這個(gè)“**”呢?本文就講解這些運(yùn)算的不同,需要的朋友可以參考下2015-04-04
pandas combine_first函數(shù)處理兩個(gè)數(shù)據(jù)集重疊和缺失
combine_first是pandas中的一個(gè)函數(shù),它可以將兩個(gè)DataFrame對象按照索引進(jìn)行合并,用一個(gè)對象中的非空值填充另一個(gè)對象中的空值,這個(gè)函數(shù)非常適合處理兩個(gè)數(shù)據(jù)集有部分重疊和缺失的情況,可以實(shí)現(xiàn)數(shù)據(jù)的補(bǔ)全和更新,本文介紹combine_first函數(shù)的語法及一些案例應(yīng)用2024-01-01
Python使用matplotlib繪制動(dòng)畫的方法
這篇文章主要介紹了Python使用matplotlib繪制動(dòng)畫的方法,涉及matplotlib模塊的常見使用技巧,需要的朋友可以參考下2015-05-05
Django1.11配合uni-app發(fā)起微信支付的實(shí)現(xiàn)
這篇文章主要介紹了Django1.11配合uni-app發(fā)起微信支付的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10

