起始版本:1.0.0html
時間複雜度:O(1)web
對存儲在指定key
的數值執行原子的加1操做。redis
若是指定的key不存在,那麼在執行incr操做以前,會先將它的值設定爲0
。api
若是指定的key中存儲的值不是字符串類型(fix:)或者存儲的字符串類型不能表示爲一個整數,服務器
那麼執行這個命令時服務器會返回一個錯誤(eq:(error) ERR value is not an integer or out of range)。測試
這個操做僅限於64位的有符號整型數據。網站
注意: 因爲redis並無一個明確的類型來表示整型數據,因此這個操做是一個字符串操做。lua
執行這個操做的時候,key對應存儲的字符串被解析爲10進制的64位有符號整型數據。spa
事實上,Redis 內部採用整數形式(Integer representation)來存儲對應的整數值,因此對該類字符串值其實是用整數保存,也就不存在存儲整數的字符串表示(String representation)所帶來的額外消耗。code
integer-reply:執行遞增操做後key
對應的值。
redis> SET mykey "10" OK redis> INCR mykey (integer) 11 redis> GET mykey "11" redis>
Redis的原子遞增操做最經常使用的使用場景是計數器。
使用思路是:每次有相關操做的時候,就向Redis服務器發送一個incr命令。
例如這樣一個場景:咱們有一個web應用,咱們想記錄每一個用戶天天訪問這個網站的次數。
web應用只須要經過拼接用戶id和表明當前時間的字符串做爲key,每次用戶訪問這個頁面的時候對這個key執行一下incr命令。
這個場景能夠有不少種擴展方法:
INCR
和EXPIRE命令,能夠實現一個只記錄用戶在指定間隔時間內的訪問次數的計數器限速器是一種能夠限制某些操做執行速率的特殊場景。
傳統的例子就是限制某個公共api的請求數目。
假設咱們要解決以下問題:限制某個api每秒每一個ip的請求次數不超過10次。
咱們能夠經過incr命令來實現兩種方法解決這個問題。
更加簡單和直接的實現以下:
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命令,將遞增操做和設置過時時間的操做放在了一個事務中, 從而保證了兩個操做的原子性。
另一個實現是對每一個ip只用一個單獨的計數器(不是每秒生成一個),可是須要注意避免竟態條件。 咱們會對多種不一樣的變量進行測試。
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) END PERFORM_API_CALL() END
上述方法的思路是,從第一個請求開始設置過時時間爲1秒。若是1秒內請求數超過了10個,那麼會拋異常。
不然,計數器會清零。
上述代碼中,可能會進入競態條件,好比客戶端在執行INCR以後,沒有成功設置EXPIRE時間。這個ip的key 會形成內存泄漏,直到下次有同一個ip發送相同的請求過來。
把上述INCR和EXPIRE命令寫在lua腳本並執行EVAL命令能夠避免上述問題(只有redis版本>=2.6纔可使用)
local current current = redis.call("incr",KEYS[1]) if tonumber(current) == 1 then redis.call("expire",KEYS[1],1) end
還能夠經過使用redis的list來解決上述問題避免進入競態條件。
實現代碼更加複雜而且利用了一些redis的新的feature,能夠記錄當前請求的客戶端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) END PERFORM_API_CALL() END
The RPUSHX
command only pushes the element if the key already exists.
RPUSHX命令會往list中插入一個元素,若是key存在的話
上述實現也可能會出現競態,好比咱們在執行EXISTS指令以後返回了false,可是另一個客戶端建立了這個key。
後果就是咱們會少記錄一個請求。可是這種狀況不多出現,因此咱們的請求限速器仍是可以運行良好的。