正文開(kāi)始
先說(shuō)一個(gè)限流這個(gè)概念,最早接觸這個(gè)概念是在前端。真實(shí)的業(yè)務(wù)場(chǎng)景是在搜索框中輸入文字進(jìn)行搜索時(shí),并不希望每輸一個(gè)字符都去調(diào)用后端接口,而是有停頓后才真正的調(diào)用接口。這個(gè)功能很有必要,一方面減少前端請(qǐng)求與渲染的壓力,同時(shí)減輕后端接口訪問(wèn)的壓力。類似前端的功能的代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// 前端函數(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) } } |
但是后端的限流從目的上來(lái)說(shuō)與前端類似,但是實(shí)現(xiàn)上會(huì)有所不同,讓我們看看 DRF 的限流。
1. DRF 中的限流
項(xiàng)目配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
# demo/settings.py # ... 'DEFAULT_THROTTLE_CLASSES' : ( '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)槲遗渲玫挠脩裘刻熘荒苷?qǐng)求兩次,所以在請(qǐng)求第三次之后就會(huì)給出 429 Too Many Requests的異常,具體的異常信息為下一次可用時(shí)間為 86398 秒后。
2. 限流進(jìn)階配置
上述演示的限流配置適用于對(duì)用戶的限流,比如我換個(gè)用戶繼續(xù)訪問(wèn),依然是有兩次的機(jī)會(huì)。
1
2
3
4
5
6
7
8
|
$ 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)代詩(shī)" , "title" : "如果" , "content" : "今生今世 永不再將你想起\n除了\n除了在有些個(gè)\n因落淚而濕潤(rùn)的夜里 如果\n如果你愿意" } |
分別介紹一下三種限流類
- AnonRateThrottle 適用于任何用戶對(duì)接口訪問(wèn)的限制
- UserRateThrottle 適用于請(qǐng)求認(rèn)證結(jié)束后對(duì)接口訪問(wèn)的限制
- ScopedRateThrottle 適用于對(duì)多個(gè)接口訪問(wèn)的限制
所以三種不同的類適用于不同的業(yè)務(wù)場(chǎng)景,具體使用根據(jù)不同的業(yè)務(wù)場(chǎng)景選擇,通過(guò)配置相對(duì)應(yīng) scope 的頻率的配置就可以達(dá)到預(yù)期的效果。
3. 限流思路分析
試想一下如果是你編碼實(shí)現(xiàn)這個(gè)需求應(yīng)該怎么實(shí)現(xiàn)?
其實(shí)這個(gè)功能不難,核心的參數(shù)就是 時(shí)間、次數(shù)、使用范圍,下面演示對(duì)函數(shù)調(diào)用次數(shù)的限制。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
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)用超過(guò)設(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)用超過(guò)設(shè)定次數(shù) test2 test2 """ |
這里實(shí)現(xiàn)了對(duì)函數(shù)調(diào)用次數(shù)的監(jiān)控同時(shí)設(shè)置了能夠使用該功能的函數(shù)。當(dāng)函數(shù)調(diào)用次數(shù)超過(guò)設(shè)定閥值久拋出異常。只是這里沒(méi)有對(duì)時(shí)間做限制。
4. 源碼分析
剛才分析了如何實(shí)現(xiàn)對(duì)函數(shù)調(diào)用次數(shù)的限制,對(duì)于一個(gè)請(qǐng)求來(lái)說(shuō)可能會(huì)復(fù)雜一點(diǎn),下面就看看 DRF 如何實(shí)現(xiàn)的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
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í)間的限制改變請(qǐng)求次數(shù)的緩存 while self .history and self .history[ - 1 ] < = self .now - self .duration: self .history.pop() # 核心邏輯就是這里判斷請(qǐng)求次數(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 : # 考慮到用戶沒(méi)有認(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í)間判斷是否超過(guò)設(shè)置定的閥值。
- 不同類型的限流,在緩存 key 的設(shè)計(jì)上會(huì)有區(qū)別,默認(rèn)的 key 為請(qǐng)求中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ù)邏輯需要,還請(qǐng)自己實(shí)現(xiàn)限流的邏輯。
- 如果是自定義的用戶表,需要重寫緩存中 get_cache_key 的邏輯。
- 如果需要統(tǒng)計(jì)分析用戶被限流情況也是需要重新設(shè)計(jì)限流的邏輯。
- 限流的邏輯在生產(chǎn)環(huán)境中慎用,因?yàn)闀?huì)限制用戶使用產(chǎn)品,對(duì)用戶不夠友好。
參考資料
以上就是Django REST framework 限流功能的使用的詳細(xì)內(nèi)容,更多關(guān)于Django REST framework 限流功能的資料請(qǐng)關(guān)注服務(wù)器之家其它相關(guān)文章!
原文鏈接:https://juejin.cn/post/6976921186982690853