*Redis incr 命令

*语法

INCR key

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

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

Redis 以整数表示形式存储整数,因此对于实际持有整数的字符串值,存储整数的字符串表示没有开销。

*示例

# 基础:使用 INCR 将键的整数值加一(如果键不存在则初始化为 0)
> SET mykey "10"
"OK"
> INCR mykey
(integer) 11
> GET mykey
"11"

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

redis> SET mykey "10"
"OK"
redis> INCR mykey
(integer) 11
redis> GET mykey
"11"
redis>

*模式:计数器

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

为此,Web 应用程序可能只需在用户执行页面浏览时递增一个键,通过连接用户 ID 和表示当前日期的字符串来创建键名。

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

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

*模式:速率限制器

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

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

*模式:速率限制器 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 地址。

这可以通过将 INCR 与可选的 EXPIRE 转换为 Lua 脚本来轻松修复,然后使用 EVAL 命令发送该脚本(自 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 调用,因此速率限制仍然可以正常工作。

*返回值说明

整数回复: 递增后键的值。