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