# 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 和表示当前日期的字符串的键名。
这个简单的模式可以通过多种方式进行扩展:
- 可以在每个页面视图中一起使用
INCR
和EXPIRE
,以使计数器仅计算最近的 N 个页面视图,间隔小于指定的秒数。 - 客户端可以使用 GETSET 以原子方式获取当前计数器值并将其重置为零。
- 使用其他原子递增/递减命令,例如
DECR
或INCRBY
可以根据用户执行的操作处理可能变大或变小的值。例如,想象一下在线游戏中不同用户的得分。
# 模式:速率限制器
速率限制器模式是一种特殊的计数器,用于限制可以执行操作的速率。这种模式的经典实现涉及限制可以针对公共 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 地址。
这可以很容易地解决,将INCR
with 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 调用,因此速率限制仍然可以正常工作。
# 反馈
如果您在此页面上发现问题,或有改进建议,请提交请求以合并或打开存储库中的问题。