incr
用法
incr key,可以將key值原子自增1,並返回遞增操作後key對應的新值。如果指定的key不存在,那麼在執行incr操作之前,會先將它的值設定為0。
/*測試前,清除當前資料庫所有key*/127.0.0.1:6379> flushDBOK/*沒有key*/127.0.0.1:6379> keys *(empty list or set) /*使用incr 一個不存在的key,有返回為1(如果指定的key不存在,那麼在執行incr操作之前,會先將它的值設定為0,並返回自增後的值1)*/127.0.0.1:6379> incr incrKey(integer) 1127.0.0.1:6379> get incrKey"1"/*自增1,返回增加後的值2*/127.0.0.1:6379> incr incrKey(integer) 2127.0.0.1:6379> get incrKey"2" 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
使用情境1 - 計數器
例如:一個web應用,我們想記錄每個使用者每天訪問這個網站的次數。就可以使用這個使用者的id和當天日期拼接一個key,每訪問一次只用incr對該key操作,從而獲得該使用者當天的訪問網站次數。比如使用者id為9eda3e419e6eadb99293f5c9105816c93a0ca760,今日是20161015,則可以使用incr 9eda3e419e6eadb99293f5c9105816c93a0ca760:20161015作為統計該使用者在2016-10-15當天的訪問次數。
該情境的擴充:統計該使用者在某個時間範圍之內的訪問次數,可以結合incr、expire來達到目標。 使用情境2 - 限制訪問次數(一)
假設我們有這樣的需求:每個api介面,每秒每個ip的訪問次數不能超過10次。
我們可以為ip:時間戳記(到秒)設定key,以下使用偽碼展示:
FUNCTION LIMIT_ACCESS_COUNT(ip)currSecond = CURRENT_UNIX_TIME()keyName = ip+":"+currSecondcurrentCnt = GET(keyName)IF currentCnt != NULL AND currentCnt > 10 THEN ERROR "一秒內訪問次數過多"ELSE MULTI /*比如10.192.168.27在2016-10-15 15:20:19時訪問次數不到10,一直自增*/ INCR(keyName,1) /*計數器每次遞增的時候都設定了10秒的到期時間,這樣在進入下一秒時,redis會自動刪除前一秒的計數器。 * 鍵 10.192.168.27:2016-10-15 15:20:19將會在2016-10-15 15:20:29之後刪除 */ EXPIRE(keyName,10) EXEC DO_JOB()END 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
使用情境2 - 限制訪問次數(二)
前面例子是每個ip每一秒都產生一個key。在此例中,我們一個ip只會產生一個key,但是實際使用中需要注意競態條件的出現。
具體思路是:從第一個請求開始設定到期時間為1秒。如果1秒內請求數超過了10個,那麼會提示錯誤資訊。到了下一秒,計數器會清零後重新開始計數。
FUNCTION LIMIT_ACCESS_COUNT(ip)keyName = ipcurrentCnt = GET(keyName)IF currentCnt != NULL AND currentCnt > 10 THEN ERROR "一秒內訪問次數過多"ELSE MULTI /*比如10.192.168.27在2016-10-15 15:20:19時訪問次數不到10,一直自增*/ currentCnt = INCR(ip) IF currentCnt == 1 THEN /*計數器每次遞增的時候都設定了1秒的到期時間,只有在第一次訪問時才設定逾時時間為1秒 * 鍵 10.192.168.27:2016-10-15 15:20:19將會在2016-10-15 15:20:20之後刪除 */ EXPIRE(keyName,1) END EXEC DO_JOB()END 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
處理競態條件 : 使用LUA指令碼。
在前面的例子中,如果使用incr後,沒有成功執行expire,會導致這個ip的key引起記憶體流失,知道下次有同一個ip發送相同請求過來。可以將可能發生競態條件的邏輯放在LUA指令碼中,再使用eval解決(要求REDIS2.6版本以上)
/*LUA指令碼*/local currentCntcurrentCnt = redis.call("incr",KEYS[1])if tonumber(currentCnt) == 1 then redis.call("expire",KEYS[1],1)end 1 2 3 4 5 6
getset
getset key value 會將value設定為key的值,但是返回的是key原來的值。如果key存在但是對應的value不是字串,就返回錯誤。如果之前Key不存在將返回nil。
127.0.0.1:6379> flushDB OK 127.0.0.1:6379> keys * (empty list or set) /使用incr實現計數器自增,使用getset可以重設為0/ 127.0.0.1:6379> incr testKey (integer) 1 127.0.0.1:6379> incr testKey (integer) 2 127.0.0.1:6379> getset testKey 0 “2” 127.0.0.1:6379> get testKey “0”
/key不存在返回nil/
127.0.0.1:6379> getset testKey2 0
(nil) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17