起始版本:1.0.0html
时间复杂度:O(1)web
对存储在指定key
的数值执行原子的加1操做。redis
若是指定的key不存在,那么在执行incr操做以前,会先将它的值设定为0
。api
若是指定的key中存储的值不是字符串类型(fix:)或者存储的字符串类型不能表示为一个整数,服务器
那么执行这个命令时服务器会返回一个错误(eq:(error) ERR value is not an integer or out of range)。测试
这个操做仅限于64位的有符号整型数据。网站
注意: 因为redis并无一个明确的类型来表示整型数据,因此这个操做是一个字符串操做。lua
执行这个操做的时候,key对应存储的字符串被解析为10进制的64位有符号整型数据。spa
事实上,Redis 内部采用整数形式(Integer representation)来存储对应的整数值,因此对该类字符串值其实是用整数保存,也就不存在存储整数的字符串表示(String representation)所带来的额外消耗。code
integer-reply:执行递增操做后key
对应的值。
redis> SET mykey "10" OK redis> INCR mykey (integer) 11 redis> GET mykey "11" redis>
Redis的原子递增操做最经常使用的使用场景是计数器。
使用思路是:每次有相关操做的时候,就向Redis服务器发送一个incr命令。
例如这样一个场景:咱们有一个web应用,咱们想记录每一个用户天天访问这个网站的次数。
web应用只须要经过拼接用户id和表明当前时间的字符串做为key,每次用户访问这个页面的时候对这个key执行一下incr命令。
这个场景能够有不少种扩展方法:
INCR
和EXPIRE命令,能够实现一个只记录用户在指定间隔时间内的访问次数的计数器限速器是一种能够限制某些操做执行速率的特殊场景。
传统的例子就是限制某个公共api的请求数目。
假设咱们要解决以下问题:限制某个api每秒每一个ip的请求次数不超过10次。
咱们能够经过incr命令来实现两种方法解决这个问题。
更加简单和直接的实现以下:
FUNCTION LIMIT_API_CALL(ip) ts = CURRENT_UNIX_TIME() keyname = ip+":"+ts current = GET(keyname) IF current != NULL AND current > 10 THEN ERROR "too many requests per second" ELSE MULTI INCR(keyname,1) EXPIRE(keyname,10) EXEC PERFORM_API_CALL() END
这种方法的基本点是每一个ip每秒生成一个能够记录请求数的计数器。
可是这些计数器每次递增的时候都设置了10秒的过时时间,这样在进入下一秒以后,redis会自动删除前一秒的计数器。
注意上面伪代码中咱们用到了MULTI和EXEC命令,将递增操做和设置过时时间的操做放在了一个事务中, 从而保证了两个操做的原子性。
另一个实现是对每一个ip只用一个单独的计数器(不是每秒生成一个),可是须要注意避免竟态条件。 咱们会对多种不一样的变量进行测试。
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(value,1) END PERFORM_API_CALL() END
上述方法的思路是,从第一个请求开始设置过时时间为1秒。若是1秒内请求数超过了10个,那么会抛异常。
不然,计数器会清零。
上述代码中,可能会进入竞态条件,好比客户端在执行INCR以后,没有成功设置EXPIRE时间。这个ip的key 会形成内存泄漏,直到下次有同一个ip发送相同的请求过来。
把上述INCR和EXPIRE命令写在lua脚本并执行EVAL命令能够避免上述问题(只有redis版本>=2.6才可使用)
local current current = redis.call("incr",KEYS[1]) if tonumber(current) == 1 then redis.call("expire",KEYS[1],1) end
还能够经过使用redis的list来解决上述问题避免进入竞态条件。
实现代码更加复杂而且利用了一些redis的新的feature,能够记录当前请求的客户端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
The RPUSHX
command only pushes the element if the key already exists.
RPUSHX命令会往list中插入一个元素,若是key存在的话
上述实现也可能会出现竞态,好比咱们在执行EXISTS指令以后返回了false,可是另一个客户端建立了这个key。
后果就是咱们会少记录一个请求。可是这种状况不多出现,因此咱们的请求限速器仍是可以运行良好的。