什麼是限流?
- 限流類似於許可權機制,它也決定是否接受當前請求,用於控制使用者端在某段時間內允許向API發出請求的次數,也就是頻率
- 假設有使用者端(比如爬蟲程式)短時間發起大量請求,超過了伺服器能夠處理的能力,將會影響其他使用者的正常使用
- 為了保證服務的穩定性,並防止介面受到惡意使用者的攻擊,我們可以對介面進行限流
- 又或者可以對未經身份驗證的請求設定存取頻率,對經過身份驗證的請求不限制存取頻率
- 限流也不止單指限制存取次數的措施,例如付費資料服務的特點存取次數
限流的應用場景
- 區分使用者場景,比如匿名和已登入,不同許可權的使用者不同的限流策略
- API的不同,根據不同API設定不同的策略
- 請求的爆發期和持續期不同的限流策略
- 可以同時支援使用多個限流策略
限流的機制
限流和許可權一樣,執行檢視前會依次檢查所有的限流類,全部通過會執行View,任何一個檢查失敗,會丟擲Exceptions.Throttled異常
在settings中,通過 DEFAULT_THROTTLE_CLASSES 設定限流類,通過DEFAULT_THROTTLE_RATES設定限流頻率
DRF提供的兩個常用限流類
AnonRateThrottle:對於匿名使用者的限流,使用anon設定頻率
UserRateThrottle:對於登入使用者的限流, 使用user設定頻率
全域性限流類設定
REST_FRAMEWORK = {
# 全域性限流類的設定
'DEFAULT_THROTTLE_CLASSES': (
'rest_framework.throttling.AnonRateThrottle', # 對於匿名使用者的限流
'rest_framework.throttling.UserRateThrottle' #對於登入使用者的限流
),
# 限流頻率的設定
'DEFAULT_THROTTLE_RATES': {
'anon': '100/day', # 未認證使用者一天只許存取100次
'user': '1000/day' # 認證使用者一天可以存取1000次
}
}
DEFAULT_THROTTLE_RATES設定限流頻率格式 次數/時間單位
- second: 按秒設定頻率次數
- minute:按分鐘設定頻率次數
- hour:按小時設定頻率次數
- day: 按天設定頻率次數
檢視級別限流類設定
#匯入限流模組
from rest_framework import throttling
class getInfoList(ModelViewSet):
# 通過throttle_classes 設定該檢視的限流類
# 檢視指定會覆蓋settings設定的全域性限流
throttle_classes = (throttling.UserRateThrottle,)
def infoList(self):
...
識別請求的使用者端
我們既然要對請求進行限流,那麼肯定要失識 別是誰發來的請求,然後進行對應的措施,不然無法確定請求者身份,那麼就無法得知是不是需要限制的請求,常見的方法有三種
- drf利用http報頭的 x-forwarded-for 或者wsgi中的remote-addr變數來唯一標識使用者端的IP地址
- 如果 存在x-forwarded-for 屬性,則使用x-forwarded-for ,否則使用remote-addr
- 可以使用request.user的屬性來標識請求,比如使用request.user.id 來標記唯一請求
- 使用IP地址對使用者端請求進行限流,需要考慮使用偽造代理IP請求的情況
throttling原始碼解析
throttling原始碼一共有五個類
- BaseThrottle: 限流基礎類別
- SimpleRateThrottle:頻率校驗類
- AnonRateThrottle:匿名使用者限流
- UserRateThrottle:認證使用者限流
- ScopedRateThrottle:api檢視級別的限流
BaseThrottle限流基礎類別
沒有去具體實現某些功能,跟許可權類基礎類別似,只是提供了佔位方法
class BaseThrottle:
# allow_request原始碼並沒有直接實現功能,只是寫好了方法佔位,待後續繼承實現
# 該方法主要是處理是否允許請求通過
# 如果後續繼承基礎類別實現該方法,允許請求通過返回True,不允許請求通過返回False
def allow_request(self, request, view):
raise NotImplementedError('.allow_request() must be overridden')
# 獲取IP地址
def get_ident(self, request):
# 獲取請求頭中真實IP地址
xff = request.META.get('HTTP_X_FORWARDED_FOR')
# 獲取代理IP地址
remote_addr = request.META.get('REMOTE_ADDR')
# 獲取設定的允許的最大代理數,預設不設定為None
num_proxies = api_settings.NUM_PROXIES
# 如果num_proxies不是None,說明設定了該值
if num_proxies is not None:
# 如果設定為0,或者 xff沒有值
if num_proxies == 0 or xff is None:
# 返回代理IP地址
return remote_addr
#使用代理IP的話會有多個地址,使用逗號分割成一個list
addrs = xff.split(',')
'''
通過min函數,拿到允許的代理數和IP地址長度最小的值,使用-變成負數
在addrs列表中通過該下標取對應值
'''
client_addr = addrs[-min(num_proxies, len(addrs))]
return client_addr.strip()
# 如果沒有設定允許的代理數 並且xff有值則直接返回,否則返回remote_addr
return ''.join(xff.split()) if xff else remote_addr
# 等待時間,告訴使用者端被限流,等待多久可以存取
# 後續繼承實現,可選
def wait(self):
return None
SimpleRateThrottle
頻率控制類,繼承了BaseThrottle,新增和重寫了一些方法,重點是新增了get_cache_key 方法,但必須自己實現該方法
class SimpleRateThrottle(BaseThrottle):
# 限流需要用到快取,使用drf預設的快取
# 如果其他繼承類想修改快取機制,cache = caches['快取名'] 進行修改
cache = default_cache
# time.time方法,但是並沒有()進行範例呼叫
# 類似計時器功能,在這裡留好,後續呼叫
timer = time.time
# 快取設定,字串格式化方法後續傳參使用
cache_format = 'throttle_%(scope)s_%(ident)s'
# scope預設沒有設定,該值是DEFAULT_THROTTLE_RATES中對應限流類的key
scope = None
# 限流頻率預設的設定值
THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES
def __init__(self):
if not getattr(self, 'rate', None):
# 從下面get_rate()方法獲取存取頻率限制的引數
self.rate = self.get_rate()
# 通過self.parse_rate方法獲取限制的頻率及持續時間賦值給num_requests
self.num_requests, self.duration = self.parse_rate(self.rate)
# 獲取當前請求的標識
def get_cache_key(self, request, view):
raise NotImplementedError('.get_cache_key() must be overridden')
# 獲取settings頻率設定限流類對應的key
def get_rate(self):
# 如果沒有scope,丟擲異常
if not getattr(self, 'scope', None):
msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
self.__class__.__name__)
raise ImproperlyConfigured(msg)
try:
# 從 self.THROTTLE_RATES 中獲取設定的scope
return self.THROTTLE_RATES[self.scope]
except KeyError:
msg = "No default throttle rate set for '%s' scope" % self.scope
raise ImproperlyConfigured(msg)
# 獲取限流頻率設定及持續時間
def parse_rate(self, rate):
# 如果沒有設定頻率限制,直接返回None
if rate is None:
return (None, None)
# 在settings設定頻率我們使用 num/type 設定值
# 字串使用/分割 ,獲取兩個對應的值
num, period = rate.split('/')
num_requests = int(num)
#settings中設定時間單位以天為單位可以是day也可以是d
# period[0]獲取第一個字元為key,以秒為單位換算,秒就1,分就是60,天就是86400
# 如果需要擴充套件月、年等時間,可以擴充套件原始碼
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
# 返回頻率和持續時間
return (num_requests, duration)
# 是否允許請求通過,執行返回True,否則返回False
def allow_request(self, request, view):
# 如果沒有設定限流頻率,直接返回True
if self.rate is None:
return True
# 獲取使用者標識賦值給self.key
self.key = self.get_cache_key(request, view)
# 沒有使用者標識直接返回True
if self.key is None:
return True
# 獲取歷史存取時間戳
self.history = self.cache.get(self.key, [])
# 獲取當前時間戳
self.now = self.timer()
# while迴圈,如果歷史存取時間戳有值,拿到歷史時間戳[-1]的資料,如果小於等於當前時間戳減去持續時間,彈出最後一個時間戳
# 當前時間-持續時間,就相當於需要限制的時間區間,如果歷史時間戳小於等於該區間,才不會繼續pop
while self.history and self.history[-1] <= self.now - self.duration:
self.history.pop()
# 如果歷史存取時間戳的列表長度大於等於我們設定頻率數量,說明到了頻率上限
if len(self.history) >= self.num_requests:
# 返回self.throttle_failure 對應False
return self.throttle_failure()
# 返回self.throttle_success 對應True
return self.throttle_success()
# 頻率未到達上限時返回該方法
def throttle_success(self):
# 在歷史請求時間戳列表,將當前時間插入該列表
self.history.insert(0, self.now)
# 更新快取內容
self.cache.set(self.key, self.history, self.duration)
# 返回True
return True
# 頻率到達上限時返回該方法
def throttle_failure(self):
return False
# 返回還需要多長時間可以進行下一次請求,可選方法
def wait(self):
if self.history:
# 如果歷史請求時間戳有值,剩餘時間等於持續時間減去(當前時間-第一次請求)
remaining_duration = self.duration - (self.now - self.history[-1])
else:
# 剩餘的時間等於持續時間
remaining_duration = self.duration
# 允許請求的次數 等於 允許的次數-已請求的次數+1
available_requests = self.num_requests - len(self.history) + 1
# 如果允許請求的次數小於等於0,返回None
if available_requests <= 0:
return None
return remaining_duration / float(available_requests)
AnonRateThrottle
匿名限流類:繼承了SimpleRateThrottle,重寫了 get_cache_key 方法
AnonRateThrottle 只會限制未經身份驗證的使用者。傳入的請求的IP地址用於生成一個唯一的金鑰。
允許的請求頻率由以下各項之一確定(按優先順序):
- 類的 rate 屬性,可以通過繼承 AnonRateThrottle 並設定該屬性來修改這個值,優先順序高
- settings組態檔中 DEFAULT_THROTTLE_RATES['anon'] 設定項的值。優先順序低
- anonratetrottle 適用於想限制來自未知使用者的請求頻率的情況
class AnonRateThrottle(SimpleRateThrottle):
# 設定頻率控制的key為anon
scope = 'anon'
# 重寫get_cache_key方法
def get_cache_key(self, request, view):
# 如果請求使用者是經過認證的使用者,不需要進行限流,直接返回None
if request.user.is_authenticated:
return None
# 如果使用者是未經認證的使用者,將該類的scope和 使用者的IP地址傳入SimpleRateThrottle的self.cache_format類屬性
return self.cache_format % {
'scope': self.scope,
'ident': self.get_ident(request)
}
UserRateThrottle
認證使用者限流類:繼承了SimpleRateThrottle,僅僅是重寫了 get_cache_key 方法
UserRateThrottle 用於限制已認證的使用者在整個API中的請求頻率。使用者ID用於生成唯一的金鑰。未經身份驗證的請求將使用傳入的請求的IP地址生成一個唯一的金鑰
允許的請求頻率由以下各項之一確定(按優先順序):
- 類的 rate 屬性,可以通過繼承 UserRateThrottle 並設定該屬性來修改這個值,優先順序高
- settings組態檔中 DEFAULT_THROTTLE_RATES['user'] 設定項的值。優先順序低
# 設定頻率控制的key位anon
scope = 'user'
# 重寫get_cache_key方法
def get_cache_key(self, request, view):
if request.user.is_authenticated:
# 如果請求使用者是認證使用者,設定使用者的唯一標識賦值給ident
ident = request.user.pk
else:
#如果請求使用者是非認證使用者,通過get_ident獲取請求ip賦值給ident
ident = self.get_ident(request)
# 設定SimpleRateThrottle中self.cache_format的值
return self.cache_format % {
'scope': self.scope,
'ident': ident
}
ScopedRateThrottle
使用者對於每個檢視的存取頻次:繼承了SimpleRateThrottle,重寫了 get_cache_key 和allow_request 方法
ScopedRateThrottle 類用於限制對APIs特定部分的存取,也就是檢視級別的限流,不是全域性性的
只有當正在存取的檢視包含 throttle_scope 屬性時,才會應用此限制。然後,通過將檢視的「scope」屬性值與唯一的使用者ID或IP地址連線,生成唯一的金鑰。
允許的請求頻率由 scope 屬性的值在 DEFAULT_THROTTLE_RATES 中的設定確定
class ScopedRateThrottle(SimpleRateThrottle):
scope_attr = 'throttle_scope'
def __init__(self):
pass
def allow_request(self, request, view):
# 從view獲取self.scope_attr賦值給scope,如果view中沒有指定,設定為None
self.scope = getattr(view, self.scope_attr, None)
# 如果沒有設定scope,直接返回True
if not self.scope:
return True
# 獲取settings頻率設定限流類對應的key
self.rate = self.get_rate()
# 獲取頻率限制、持續時長
self.num_requests, self.duration = self.parse_rate(self.rate)
# 呼叫父類別的allow_request 返回對應的結果
return super().allow_request(request, view)
# 獲取使用者唯一標識
def get_cache_key(self, request, view):
# 如果是認證使用者 ident=使用者唯一標識
if request.user.is_authenticated:
ident = request.user.pk
else:
# 非認證使用者返回請求的ip
ident = self.get_ident(request)
# 設定父類別的類屬性
return self.cache_format % {
'scope': self.scope,
'ident': ident
}
自定義限流類
上面原始碼的類,我們一般使用的是後三個,如果原始碼提供的限流類無法滿足我們的需求,我們可以寫自定義的限流類
自定義限流類的步驟:
- 繼承BaseThrottle類或者根據場景繼承其他限流類
- 實現allow_request方法,如果請求被允許,那麼返回True,否則返回False
- wait方法,是否實現根據自己場景
- 獲取唯一標識的方法可以使用原始碼自由的,也可以自定義
場景案例1
假設我們的請求需要同時進行多個認證使用者的限流措施,比如每小時限制100次,同時每天限制1000次
# 每小時的限流類
class UserHourRateThrottle(UserRateThrottle):
scope = 'userHour'
# 每天的限流類
class UserDayRateThrottle(UserRateThrottle):
scope = 'userDay'
# settings中進行設定
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': (
# 設定我們自定義的限流類或者再view中進行區域性的設定
'testApi.throttles.UserHourRateThrottle',
'testApi.throttles.UserDayRateThrottle'
),
'DEFAULT_THROTTLE_RATES': {
'userHour': '100/hour', # 每小時最多100次
'userDay': '1000/day' # 每天最多100次
}
}
場景案例2
隨機限制
import random
class RandomRateThrottle(throttling.BaseThrottle):
def allow_request(self, request, view):
# 如果隨機的數位 不等於1,返回True,否則返回False
return random.randint(1, 10) != 1
# 之後在settings進行設定或者區域性設定