- 用法
-
INCR key
- 复杂度
- O(1)
- 自
- 1.0.0
- ACL 类别
- @string, @write, @fast
- 可以在每次页面浏览时结合使用
INCR
和EXPIRE
,以实现一个计数器,仅统计在指定秒数内间隔小于指定秒数的最新 N 次页面浏览。 - 客户端可以使用 GETSET 以原子方式获取当前计数器值并将其重置为零。
- 使用其他原子增/减命令,如
DECR
或INCRBY
,可以根据用户执行的操作处理可能变大或变小的值。例如,想象一下在线游戏中不同用户的分数。
将存储在 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 和表示当前日期的字符串来创建键名。
这种简单的模式可以通过多种方式扩展
模式:速率限制器
速率限制器模式是一种特殊的计数器,用于限制操作的执行速率。这种模式的经典实现涉及限制对公共 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 自动删除。
请注意使用 MULTI
和 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 地址。
这可以通过将带可选 EXPIRE
的 INCR
转换为 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 回复
整数回复:增量操作后键的值。