递增

语法
INCR key
可用版本
1.0.0
时间复杂度
O(1)
ACL 类别
@write, @string, @fast,

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

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

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

示例

SET mykey "10" INCR mykey GET mykey

模式:计数器

计数器模式是 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

计数器以仅在当前秒的第一条请求执行后存活一秒的方式创建。如果在同一秒内有超过十条请求,计数器将达到大于十的值,否则它将过期并从 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 回复

整数回复:递增后键的值。
RATE THIS PAGE
Back to top ↑