为什么限制访问频率

做服务接口时通常需要用到请求频率限制 Rate limiting,例如限制一个用户1分钟内最多可以范围100次

主要用来保证服务性能和保护数据安全

因为如果不进行限制,服务调用者可以随意访问,想调几次就调几次,会给服务造成很大的压力,降低性能,再比如有的接口需要验证调用者身份,如果不进行访问限制,调用者可以进行暴力尝试

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脚本实现频率限制

思路

把限制逻辑封装到一个Lua脚本中,调用时只需传入:key、限制数量、过期时间,调用结果就会指明是否运行访问

local notexists = redis.call(\"set\", KEYS[1], 1, \"NX\", \"EX\", tonumber(ARGV[2]))
if (notexists) then
return 1
end
local current = tonumber(redis.call(\"get\", KEYS[1]))
if (current == nil) then
local result = redis.call(\"incr\", KEYS[1])
redis.call(\"expire\", KEYS[1], tonumber(ARGV[2]))
return result
end
if (current >= tonumber(ARGV[1])) then
error(\"too many requests\")
end
local result = redis.call(\"incr\", KEYS[1])
return result

使用 eval 调用

eval 脚本 1 key 参数-允许的最大次数 参数-过期时间

Redis实现访问控制频率的更多相关文章

  1. redis 控制调用频率

    redis提供了rate limit demo 如下所示: INCR key Available since 1.0.0. Time complexity: O(1) Increments the n ...

  2. vagrant系列教程(四):vagrant搭建redis与redis的监控程序redis-stat(转)

    上一篇php7环境的搭建 真是火爆,仅仅两天时间,就破了我之前swagger系列的一片文章,看来,大家对搭建环境真是情有独钟. 为了访问量,我今天再来一篇Redis的搭建.当然不能仅仅是redis的搭 ...

  3. Redis资料汇总专题

    1.Redis是什么? 十五分钟介绍 Redis数据结构 Redis系统性介绍 一个很棒的Redis介绍PPT 强烈推荐!非同一般的Redis介绍 Redis之七种武器 锋利的Redis redis ...

  4. redis资料汇总

    redis资源比较零散,引用nosqlfan上的文章,方便大家需要时翻阅.大家看完所有的,如果整理出文章的,麻烦知会一下,方便学习. 1.Redis是什么? 十五分钟介绍 Redis数据结构 Redi ...

  5. Redis配置文件redis.conf参数配置详解

    ########################################## 常规 ########################################## daemonize n ...

  6. [转载] Redis资料汇总专题

    转载自http://www.cnblogs.com/tommyli/archive/2011/12/14/2287614.html 1.Redis是什么? 十五分钟介绍 Redis数据结构 Redis ...

  7. emqtt 试用(四)emq 的主题访问控制 acl.conf

    访问控制(ACL) EMQ 消息服务器通过 ACL(Access Control List) 实现 MQTT 客户端访问控制. ACL 访问控制规则定义: 允许(Allow)|拒绝(Deny) 谁(W ...

  8. Redis服务器开启远程访问

    重启  ps aux|grep redis root 32684 0.0 0.0 48452 6944 ? Ssl 21:27 0:00 ./redis-server *:6379 kill了这个进程 ...

  9. Redis学习笔记--Redis配置文件redis.conf参数配置详解

    ########################################## 常规 ########################################## daemonize n ...

随机推荐

  1. 团队第一次作业(By七个小矮人)

    一.团队简介 1.团队名称:七个小矮人 2.团队成员列表 201731024137 马驰(队长) 201731021227 于丁 201731024114 杨汶桐 201731024125 李朋珂 2 ...

  2. X-CTF(REVERSE高级) Reversing-x64Elf-100

    逻辑很简单,如果sub_4006FD函数返回假则返回Nice! 图1 进入sub_4006FD函数,加密过程也很简单,这里值得注意的有两点 一.8*(i%3)是二维数组的第一个参数,这里是取v3的地址 ...

  3. shiro中ecache-core版本引起的异常

    ecache-core包版本不对引起的错误,将2.5.3换成2.4.5就好了 来源 WARN [RMI TCP Connection(3)-127.0.0.1] - Exception encount ...

  4. Unicode编码解析

    概述 转载自博文浅谈Unicode编码 参考博文,结合自己实际情况进行部分内容添加修改 一 需求 java源码中尤其是数字部分用到了很多Unicode方面知识,如果不懂,看源码的时候会很懵逼 java ...

  5. 从头学pytorch(二十二):全连接网络dense net

    DenseNet 论文传送门,这篇论文是CVPR 2017的最佳论文. resnet一文里说了,resnet是具有里程碑意义的.densenet就是受resnet的启发提出的模型. resnet中是把 ...

  6. java刷题(1-5)

    第一题:在一个数组中找出三个数相加之和为0,且不重复的集合 import java.lang.reflect.Array; import java.security.PublicKey; import ...

  7. Java 设置Excel自适应行高、列宽

    在excel中,可通过设置自适应行高或列宽自动排版,是一种比较常用的快速调整表格整体布局的方法.设置自适应时,可考虑2种情况: 1.固定数据,设置行高.列宽自适应数据(常见的设置自适应方法) 2.固定 ...

  8. 使用小书匠及markdown here编辑博客和微信公众号

    1. 使用小书匠连接Evernote并发布笔记到博客园 1.1 小书匠初探 我平时的信息收集的主要方法是采用Pocket+Evernote. 简单来说: 如果访问到非常有用,而且是必须要立刻记录的内容 ...

  9. Codeforces_839

    A.每天更新判断. #include<bits/stdc++.h> using namespace std; ]; int main() { ios::sync_with_stdio(); ...

  10. php扩展模块的安装

    PHP的扩展模块安装 模块安装总则: 进入到ext/目录下对应的模块 执行/usr/local/php/bin/phpize 也就是执行一遍phpize生成编译文件 ./configure --wit ...