頻次限制器模式是一種特殊的計數器,它常被用來限制某個操作可以被執行的頻次。這個模式的實質其實是限制對乙個公共api執行訪問請求的次數限制。我們使用incr
命令提供該模式的兩種實現。這裡我們假設需要解決的問題是:對每個ip,限制對某api的呼叫次數最高位10次每秒。
對該模式乙個相對簡單和直接的實現,請見如下**:
function limit_api_call(ip)
ts = current_unix_time()
keyname = ip+":"+ts
current = get(keyname)
if current != null
and current > 10
then
error
"too many requests per second"
else
multi
incr(keyname,1)
expire(keyname,10)
exec
perform_api_call()
end
簡單來說,我們對每個ip的每一秒
都有乙個計數器,但每個計數器都有乙個額外的設定:它們都將被設定乙個10秒的過期時間。這可以使得當時間已經不是當前秒時(此時該計數器也無效了),能夠讓redis自動移除它。
需要注意的是,這裡我們使用multi
和exec
命令來確保對每個api呼叫既執行了incr
也同時能夠執行expire
命令。
multi命令用於標識乙個命令集被包含在乙個事務塊中,exec保證該事務塊命令集執行的原子性。另外的一種實現是採用單一的計數器,但是為了避免
race condition
(競態條件),它也更複雜。我們來看幾種不同的變體:
function limit_api_call(ip):
current = get(ip)
if current != null
and current > 10
then
error
"too many requests per second"
else
value = incr(ip)
if value == 1
then
expire(value,1)
endperform_api_call()
end
該計數器在當前秒內第一次請求被執行時建立,但它只能存活一秒。如果在當前秒內,傳送超過10次請求,那麼該計數器將超過10。否則它將失效並從0開始重新計數。
在上面的**中,存在乙個race condition。如果因為某個原因,上面的**只執行了incr
命令,卻沒有執行expire
命令,那麼這個key將會被洩漏,直到我們再次遇到相同的ip(備註,如果這裡沒有輔助的刪除該key的措施,那麼該key將永不過期,也將每次都發生錯誤,詳情可見本人之前一篇文章)。
這種問題也不難處理,可以將incr
命令以及另外的expire
命令打包到乙個lua指令碼裡,該指令碼可以用eval
命令提交給redis執行(該方式只在redis版本大於等於2.6之後才能支援)。
local current
current = redis.call("incr",keys[1])
if tonumber(current) == 1
then
redis.call("expire",keys[1],1)
end
當然,也有另一種方式來解決這個問題而不需要動用lua指令碼,但需要用redis的list資料結構來替代計數器。這種實現方式將會更複雜,並使用更高階的特性。但它有乙個好處是記住呼叫當前api的每個客戶端的ip。這種方式可能很有用也可能沒用,這取決於應用需求。
function limit_api_call(ip)
current = llen(ip)
if current > 10
then
error
"too many requests per second"
else
if exists(ip) == false
multi
rpush(ip,ip)
expire(ip,1)
exec
else
rpushx(ip,ip)
endperform_api_call()
end
rpushx
命令只在key存在時才會將值加入list
仍然需要注意的是,這裡也存在乙個race condition(但這卻不會產生太大的影響)。問題是:exists
可能返回false
,但在我們執行multi/exec
塊內的建立list的**之前,該list可能已被其他客戶端建立。然而,在這個race condition發生時,將僅僅只是丟失乙個api呼叫,所以rate limiting仍然工作得很好。
這裡產生race condition不會有大問題的原因在於,else分支使用的rpushx,它不會導致if not than init的問題,並且expire命令將在建立list的時候以原子的形式**執行。不會產生key洩漏,導致永不失效的情況產生。
redis實現訪問頻次限制的幾種方式
頻次限制器模式是一種特殊的計數器,它常被用來限制某個操作可以被執行的頻次。這個模式的實質其實是限制對乙個公共api執行訪問請求的次數限制。我們使用incr命令提供該模式的兩種實現。這裡我們假設需要解決的問題是 對每個ip,限制對某api的呼叫次數最高位10次每秒。對該模式乙個相對簡單和直接的實現,請...
redis實現訪問頻次限制的幾種方式
這裡將實現的兩種模式翻譯一下,並適當加了一些批註說明。原文可見官網。頻次限制器模式是一種特殊的計數器,它常被用來限制某個操作能夠被執行的頻次。這個模式的實質事實上是限制對乙個公共api執行訪問請求的次數限制。我們使用incr命令提供該模式的兩種實現。這裡我們假設須要解決的問題是 對每乙個ip。限制對...
caddy的訪問認證及頻次限制
caddy 是乙個多功能的 http web伺服器,並且使用let s encrypt提供的免費證書,想要自動讓 公升級到https,需要滿足一下幾個條件 1.主機不能為空,不能為localhost,不能是萬用字元,不能是乙個ip位址 2.埠不能為指定為80 3.模式不能指定為http 4.定義的t...