redis---incr命令

https://blog.csdn.net/diavid/article/details/81034647

INCR key

起始版本:1.0.0

時間複雜度:O(1)

http://www.redis.cn/map.html   (redis知識樹)

作   用

    對存儲在指定key的數值執行原子的加1操作。

    如果指定的key不存在,那麼在執行incr操作之前,會先將它的值設定爲0

    如果指定的key中存儲的值不是字符串類型(fix:)或者存儲的字符串類型不能表示爲一個整數,

    那麼執行這個命令時服務器會返回一個錯誤(eq:(error) ERR value is not an integer or out of range)。

    這個操作僅限於64位的有符號整型數據。

注意: 由於redis並沒有一個明確的類型來表示整型數據,所以這個操作是一個字符串操作。

執行這個操作的時候,key對應存儲的字符串被解析爲10進制的64位有符號整型數據

事實上,Redis 內部採用整數形式(Integer representation)來存儲對應的整數值,所以對該類字符串值實際上是用整數保存,也就不存在存儲整數的字符串表示(String representation)所帶來的額外消耗。

返回值

integer-reply:執行遞增操作後key對應的值。

例子


 
  1. redis> SET mykey "10"

  2. OK

  3. redis> INCR mykey

  4. (integer) 11

  5. redis> GET mykey

  6. "11"

  7. redis>

實例:計數器

Redis的原子遞增操作最常用的使用場景是計數器。

使用思路是:每次有相關操作的時候,就向Redis服務器發送一個incr命令。

例如這樣一個場景:我們有一個web應用,我們想記錄每個用戶每天訪問這個網站的次數。

web應用只需要通過拼接用戶id和代表當前時間的字符串作爲key,每次用戶訪問這個頁面的時候對這個key執行一下incr命令。

這個場景可以有很多種擴展方法:

  • 通過結合使用INCREXPIRE命令,可以實現一個只記錄用戶在指定間隔時間內的訪問次數的計數器
  • 客戶端可以通過GETSET命令獲取當前計數器的值並且重置爲0
  • 通過類似於DECR或者INCRBY等原子遞增/遞減的命令,可以根據用戶的操作來增加或者減少某些值比如在線遊戲,需要對用戶的遊戲分數進行實時控制,分數可能增加也可能減少。

實例: 限速器

限速器是一種可以限制某些操作執行速率的特殊場景。

傳統的例子就是限制某個公共api的請求數目。

假設我們要解決如下問題:限制某個api每秒每個ip的請求次數不超過10次。

我們可以通過incr命令來實現兩種方法解決這個問題。

實例: 限速器 1

更加簡單和直接的實現如下:


 
  1. FUNCTION LIMIT_API_CALL(ip)

  2. ts = CURRENT_UNIX_TIME()

  3. keyname = ip+":"+ts

  4. current = GET(keyname)

  5. IF current != NULL AND current > 10 THEN

  6. ERROR "too many requests per second"

  7. ELSE

  8. MULTI

  9. INCR(keyname,1)

  10. EXPIRE(keyname,10)

  11. EXEC

  12. PERFORM_API_CALL()

  13. END

這種方法的基本點是每個ip每秒生成一個可以記錄請求數的計數器。

但是這些計數器每次遞增的時候都設置了10秒的過期時間,這樣在進入下一秒之後,redis會自動刪除前一秒的計數器。

注意上面僞代碼中我們用到了MULTIEXEC命令,將遞增操作和設置過期時間的操作放在了一個事務中,從而保證了兩個操作的原子性。

實例: 限速器 2

另外一個實現是對每個ip只用一個單獨的計數器(不是每秒生成一個),但是需要注意避免竟態條件。我們會對多種不同的變量進行測試。


 
  1. FUNCTION LIMIT_API_CALL(ip):

  2. current = GET(ip)

  3. IF current != NULL AND current > 10 THEN

  4. ERROR "too many requests per second"

  5. ELSE

  6. value = INCR(ip)

  7. IF value == 1 THEN

  8. EXPIRE(value,1)

  9. END

  10. PERFORM_API_CALL()

  11. END

上述方法的思路是,從第一個請求開始設置過期時間爲1秒。如果1秒內請求數超過了10個,那麼會拋異常。

否則,計數器會清零。

上述代碼中,可能會進入競態條件,比如客戶端在執行INCR之後,沒有成功設置EXPIRE時間。這個ip的key會造成內存泄漏,直到下次有同一個ip發送相同的請求過來。

把上述INCR和EXPIRE命令寫在lua腳本並執行EVAL命令可以避免上述問題(只有redis版本>=2.6纔可以使用)


 
  1. local current

  2. current = redis.call("incr",KEYS[1])

  3. if tonumber(current) == 1 then

  4. redis.call("expire",KEYS[1],1)

  5. end

還可以通過使用redis的list來解決上述問題避免進入競態條件。

實現代碼更加複雜並且利用了一些redis的新的feature,可以記錄當前請求的客戶端ip地址。這個有沒有好處取決於應用程序本身。


 
  1. FUNCTION LIMIT_API_CALL(ip)

  2. current = LLEN(ip)

  3. IF current > 10 THEN

  4. ERROR "too many requests per second"

  5. ELSE

  6. IF EXISTS(ip) == FALSE

  7. MULTI

  8. RPUSH(ip,ip)

  9. EXPIRE(ip,1)

  10. EXEC

  11. ELSE

  12. RPUSHX(ip,ip)

  13. END

  14. PERFORM_API_CALL()

  15. END

The RPUSHX command only pushes the element if the key already exists.

RPUSHX命令會往list中插入一個元素,如果key存在的話

上述實現也可能會出現競態,比如我們在執行EXISTS指令之後返回了false,但是另外一個客戶端創建了這個key。

後果就是我們會少記錄一個請求。但是這種情況很少出現,所以我們的請求限速器還是能夠運行良好的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章