# INCR

将key的整数值递增1

语法

INCR key
  • 可用版本:

    1.0.0

  • 时间复杂度:

    O(1)

  • ACL 类别:

    @write, @string, @fast

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

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

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

# 返回

整数:key增量后的值

# 例子

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

# 图案:计数器

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

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

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

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

# 模式:速率限制器

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

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

# 模式:速率限制器 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 会自动删除它们。

注意使用 MULTI and EXEC 以确保我们在每次 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 地址。

这可以很容易地解决,将INCRwith optional 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 调用,因此速率限制仍然可以正常工作。

# 反馈

如果您在此页面上发现问题,或有改进建议,请提交请求以合并或打开存储库中的问题。

Last Updated: 4/18/2023, 8:45:33 AM