?
This document uses PHP Chinese website manual Release
INCR key
自1.0.0起可用。
時間復(fù)雜度: O(1)
將存儲的數(shù)字key
加1。如果密鑰不存在,則0
在執(zhí)行操作之前將其設(shè)置為。如果密鑰包含錯誤類型的值或包含無法表示為整數(shù)的字符串,則會返回錯誤。該操作僅限于64位有符號整數(shù)。
注意:這是一個字符串操作,因為Redis沒有專用的整數(shù)類型。存儲在密鑰中的字符串被解釋為一個以10為底的64位有符號整數(shù)來執(zhí)行操作。
Redis 以整數(shù)表示形式存儲整數(shù),因此對于實際上包含整數(shù)的字符串值,不存在用于存儲整數(shù)的字符串表示形式的開銷。
整數(shù)回復(fù):key
增量后的值
redis> SET mykey "10" "OK"
redis> INCR mykey (integer) 11
redis> GET mykey "11"
計數(shù)器模式是使用 Redis 原子增量操作可以做的最明顯的事情。這個想法只是在每次操作發(fā)生時發(fā)送 INCR 命令給 Redis。例如,在一個 Web 應(yīng)用程序中,我們可能想知道這個用戶一年中每天都做了多少頁面瀏覽。
為此,Web 應(yīng)用程序可以在每次用戶執(zhí)行頁面視圖時簡單地增加一個鍵,創(chuàng)建連接用戶 ID 的鍵名和代表當(dāng)前日期的字符串。
這種簡單模式可以通過多種方式進行擴展:
可以在每個頁面視圖中一起使用 INCR 和 EXPIRE,使計數(shù)器只計算最近 N 個分頁少于指定秒數(shù)的頁面視圖。
客戶端可以使用 GETSET 來自動獲取當(dāng)前計數(shù)器值并將其重置為零。
使用其他原子增量/減量命令(如 DECR 或 INCRBY),可以根據(jù)用戶執(zhí)行的操作來處理可能變大或變小的值。想象一下在線游戲中不同用戶的分?jǐn)?shù)。
速率限制器模式是一個特殊的計數(shù)器,用于限制執(zhí)行操作的速率。這種模式的經(jīng)典實現(xiàn)涉及限制可以針對公共 API 執(zhí)行的請求的數(shù)量。
我們使用 INCR 提供了這種模式的兩種實現(xiàn),我們假設(shè)要解決的問題是將 API 調(diào)用的數(shù)量限制為每IP地址每秒最多10個請求。
這種模式的更簡單直接的實現(xiàn)如下:
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的柜臺,每隔一秒鐘。但是這個計數(shù)器總是遞增的,設(shè)置10秒過期,以便在當(dāng)前秒不同時自動將它們由 Redis移除。
請注意使用 MULTI 和 EXEC,以確保我們在每次 API 調(diào)用時都會遞增并設(shè)置到期。
另一種實現(xiàn)方法是使用單個計數(shù)器,但要在沒有競爭條件的情況下進行正確的設(shè)置會更復(fù)雜一些。我們將研究不同的變體。
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(ip,1) END PERFORM_API_CALL()END
計數(shù)器的創(chuàng)建方式只能在當(dāng)前秒的第一次請求開始后才能存活一秒。如果在同一秒內(nèi)有超過10個請求,則計數(shù)器將達到大于10的值,否則將過期并從0開始。
在上面的代碼中有一個競爭條件。如果由于某種原因,客戶端執(zhí)行INCR 命令但不執(zhí)行 EXPIRE,密鑰將被泄露,直到我們再次看到相同的IP地址。
這可以很容易地解決,將帶有可選 EXPIRE 的 INCR 轉(zhuǎn)換為使用EVAL 命令發(fā)送的 Lua 腳本(僅在 Redis 版本2.6以后可用)。
local current current = redis.call("incr",KEYS[1])if tonumber(current) == 1 then redis.call("expire",KEYS[1],1)end
有一種不使用腳本解決此問題的方法,但使用 Redis 列表而不是計數(shù)器。實現(xiàn)更復(fù)雜,使用更多高級功能,但具有記住當(dāng)前正在執(zhí)行API調(diào)用的客戶端的IP地址的優(yōu)點,根據(jù)應(yīng)用程序的不同,該地址可能有用或無用。
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
如果密鑰已經(jīng)存在,RPUSHX 命令只會推送元素。
請注意,我們在這里有一場競賽,但這不是問題:在我們在MULTI / EXEC 塊中創(chuàng)建它之前,EXISTS 可能會返回 false,但可能會由另一個客戶端創(chuàng)建密鑰。然而,這種競爭只會在極少數(shù)情況下錯過API調(diào)用,所以速率限制仍然可以正常工作。