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

2021-07-04 18:54:42 字數 2819 閱讀 8188

頻次限制器模式是一種特殊的計數器,它常被用來限制某個操作可以被執行的頻次。這個模式的實質其實是限制對乙個公共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的每個客戶端的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...