INCR

Syntax
INCR key
Available since:
1.0.0
Time complexity:
O(1)
ACL categories:
@write, @string, @fast,

将存储在key中的数字增加一。 如果键不存在,则在执行操作之前将其设置为0。 如果键包含错误类型的值或包含无法表示为整数的字符串,则返回错误。 此操作仅限于64位有符号整数。

注意: 这是一个字符串操作,因为 Redis 没有专门的整数类型。 存储在键中的字符串被解释为以10为基数的64位有符号整数来执行操作。

Redis 以整数形式存储整数,因此对于实际包含整数的字符串值,存储整数的字符串表示形式不会产生开销。

示例

在交互式控制台中尝试这个命令:

SET mykey "10" INCR mykey GET mykey

模式:计数器

计数器模式是你可以用Redis原子递增操作做的最明显的事情。 这个想法很简单,每次操作发生时向Redis发送一个INCR命令。 例如,在一个网络应用程序中,我们可能想知道这个用户在一年中的每一天进行了多少次页面浏览。

为此,Web应用程序可以在用户每次执行页面浏览时简单地增加一个键,通过将用户ID和表示当前日期的字符串连接起来来创建键名。

这个简单的模式可以通过多种方式扩展:

  • 可以在每次页面浏览时同时使用INCREXPIRE来设置一个计数器,该计数器仅计算在指定秒数内分隔的最新N次页面浏览。
  • 客户端可以使用GETSET来原子性地获取当前计数器的值并将其重置为零。
  • 使用其他原子递增/递减命令,如DECRINCRBY,可以处理可能根据用户执行的操作而变大或变小的值。例如,想象一下在线游戏中不同用户的分数。

模式:速率限制器

速率限制器模式是一种特殊的计数器,用于限制执行操作的速率。 这种模式的经典实现涉及限制可以对公共API执行的请求数量。

我们提供了两种使用INCR实现此模式的方案,其中我们假设要解决的问题是将每个IP地址的API调用限制为每秒最多十次请求

模式:限速器 1

这种模式的更简单直接的实现如下:

FUNCTION LIMIT_API_CALL(ip)
ts = CURRENT_UNIX_TIME()
keyname = ip+":"+ts
MULTI
    INCR(keyname)
    EXPIRE(keyname,10)
EXEC
current = RESPONSE_OF_INCR_WITHIN_MULTI
IF current > 10 THEN
    ERROR "too many requests per second"
ELSE
    PERFORM_API_CALL()
END

基本上,我们为每个IP、每个不同的秒都有一个计数器。 但是这些计数器在增加时总是设置一个10秒的过期时间,这样 当当前秒不同时,它们会被Redis自动移除。

注意使用MULTIEXEC以确保我们在每次API调用时都会增加并设置过期时间。

模式:限速器 2

另一种实现使用单个计数器,但要正确实现而不出现竞争条件则稍微复杂一些。我们将检查不同的变体。

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

计数器以这样一种方式创建,它只能存活一秒钟,从当前秒内执行的第一个请求开始。如果在同一秒内有超过10个请求,计数器将达到大于10的值,否则它将过期并从0重新开始。

在上述代码中存在竞态条件。 如果由于某种原因客户端执行了INCR命令但没有执行 EXPIRE,那么该键将会泄露,直到我们再次看到相同的IP地址。

这可以通过将带有可选EXPIREINCR转换为使用EVAL命令发送的Lua脚本来轻松修复(仅在Redis 2.6版本之后可用)。

local current
current = redis.call("incr",KEYS[1])
if current == 1 then
    redis.call("expire",KEYS[1],1)
end

有一种不同的方法可以解决这个问题,而不需要使用脚本,那就是使用Redis列表而不是计数器。 这种实现方式更为复杂,使用了更高级的功能,但它的优点是能够记住当前正在执行API调用的客户端的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

只有在键已经存在时,RPUSHX 命令才会推送元素。

请注意,这里存在一个竞争条件,但这并不是问题:EXISTS 可能返回 false,但在我们在 MULTI / EXEC 块中创建键之前,另一个客户端可能已经创建了该键。然而,这种竞争条件在极少数情况下只会错过一个 API 调用,因此速率限制仍然可以正常工作。

RESP2/RESP3 回复

Integer reply: the value of the key after the increment.
RATE THIS PAGE
Back to top ↑