Redis实现访问控制频率
Posted 牛奔
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis实现访问控制频率相关的知识,希望对你有一定的参考价值。
redis如何解决“单位时间内只能n次操作”这样的问题?
假定要限制每分钟每个用户最多只能访问100个页面。
方案一:string
通过为用户使用一个名为 rate.limiting:userId 的字符串类型键,每次访问都使用 INCR命令递增该键的键值。
如果递增后的值为 1(第一次访问),则要为键设置过期时间 60秒。
这样每次用户访问都读取该键值,当键值超过100时,说明访问频率超过了限制,需要稍后访问。
该键过期后会自动删除,所以下一分钟用户访问次数又会重新计算。
伪代码如下: $isKeyExists = EXISTS rate.limiting:$userId // 存在返回 1,不存在返回 0 if $isKeyExists is 1 $times = INCR rate.limiting:$userId if $times > 100 // 第100次访问会增加到101 print 访问频率超过限制,请稍后再试 exit else MULTI //此处,如果不加事务,竞态条件可能出现
INCR rate.limiting:$userId
EXPIRE $keyName, 60
EXEC
上面为什么要用MULTI,那是因为如果在执行完INCR rate.limiting:$userId之后,如果(出现故障)没有设置过期时间,那么该键将永远存在,所以需要加上事务。
方案二:list
事实上,方案一有个问题。如果一个用户在第一分钟的最后一秒访问了99次,在下一分钟的第一秒访问了100次,相当于在两秒访问了199次,
与一分钟内最多只能访问100次相比还是差距比较大,尽管这种情况比较极端,但是依然存在。如果要实现粒度更小的控制方式,精确的保证每分钟最多访问100次,就需要使用第二种方案。
第二种方案需要记录用户每次的访问时间,因此对于每个用户,用列表类型的键记录他最近100次访问的时间。
如果键中的元素超过100个,就判断时间最早的元素距离现在的时间是否小于1分钟,如果是,则表示用户最近1分钟的访问次数超过100次,如果不是就将当前时间加入列表中,同时把最早的元素删除
伪代码如下: $limitLength = LLEN rate.limiting:$userId if $limitLength < 100 LPUSH rate.limiting:$userId, now() else $time = LINDEX rate.limiting:$userId, -1 // 取最后一个元素 if now() - $time < 60 print 访问频率超过限制,请稍后再试 else LPUSH rate.limiting:$userId, now() LTRIM rate.limiting:$userId, 0, 99 // 删除[0~99]以外的元素
这种方式 now() 的功能是获得当前的 Unix时间,由于要记录当前访问时间,所以当要限制 “A时间最多访问B次” 时,如果”B”比较大,会占用较多内存,
实际使用时要去权衡。而且这种方法会出现就竞态条件,可以通过脚本避免。
但是在高并发的缓存系统中,大量使用事务是非常糟糕的,可以用redis自带的lua脚本功能实现多个操作的“原子性”
方案三:使用lua脚本实现频率限制
local times = redis.call(‘incr‘, KEYS[1]) if times == 1 then redis.call(‘expire‘, KEYS[1], ARGV[1]) end if times > tonumber(ARGV[2]) then return 0 end return 1 ```javascript
以上是关于Redis实现访问控制频率的主要内容,如果未能解决你的问题,请参考以下文章