INCR

用法
INCR key
复杂度
O(1)
1.0.0
ACL 类别
@string, @write, @fast

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

注意:这是一项字符串操作,因为 Valkey 没有专门的整数类型。存储在键中的字符串被解释为十进制的 64 位有符号整数以执行操作。

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

示例

127.0.0.1:6379> SET mykey "10"
OK
127.0.0.1:6379> INCR mykey
(integer) 11
127.0.0.1:6379> GET mykey
"11"

模式:计数器

计数器模式是使用 Valkey 原子增量操作可以做的最显而易见的事情。其思想是,每次操作发生时,只需向 Valkey 发送一个 INCR 命令。例如,在 Web 应用程序中,我们可能想知道用户每年每天的页面浏览量。

为此,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 秒的过期时间,这样当当前秒变为不同时,它们将由 Valkey 自动删除。

请注意使用 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 转换为 Lua 脚本并使用 EVAL 命令发送来轻松修复(仅从 Valkey 2.6 版本开始可用)。

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

还有另一种不使用脚本来解决此问题的方法,即使用列表而不是计数器。这种实现更复杂,使用了更高级的功能,但优点是能够记住当前执行 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 回复

整数回复:增量操作后键的值。