redis實現訪問頻次限制的幾種方式

2021-09-07 13:23:13 字數 2942 閱讀 2510

這裡將實現的兩種模式翻譯一下,並適當加了一些批註說明。原文可見官網。

頻次限制器模式是一種特殊的計數器,它常被用來限制某個操作能夠被執行的頻次。

這個模式的實質事實上是限制對乙個公共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自己主動移除它。

須要注意的是,這裡我們使用multiexec命令來確保對每乙個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的每乙個client的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可能已被其它client建立。然而,在這個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,限制對某api的呼叫次數最高位10次每秒。對該模式乙個相對簡單和直接的實現,請...

caddy的訪問認證及頻次限制

caddy 是乙個多功能的 http web伺服器,並且使用let s encrypt提供的免費證書,想要自動讓 公升級到https,需要滿足一下幾個條件 1.主機不能為空,不能為localhost,不能是萬用字元,不能是乙個ip位址 2.埠不能為指定為80 3.模式不能指定為http 4.定義的t...