结合上一篇文章《redis在学生抢房应用中的实践小结》中提及的用redis实现DDOS设计时遇到的expire的坑。事实上,redis官网中对incr命令的介绍中已经有关于怎样用redis来做rate limit的探讨。

这里将实现的两种模式翻译一下,并适当加了一些批注说明。原文可见官网

模式:Rate limiter

频次限制器模式是一种特殊的计数器,它常被用来限制某个操作能够被运行的频次。

这个模式的实质事实上是限制对一个公共API运行訪问请求的次数限制。我们使用incr命令提供该模式的两种实现。这里我们假设须要解决的问题是:对每一个IP。限制对某API的调用次数最高位10次每秒。

模式:Rate limiter 1

对该模式一个相对简单和直接的实现,请见例如以下代码:

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自己主动移除它。

须要注意的是,这里我们使用multiexec命令来确保对每一个API调用既运行了incr也同一时候能够运行expire命令。

multi命令用于标识一个命令集被包括在一个事务块中,exec保证该事务块命令集运行的原子性。

模式:Rate limiter 2

另外的一种实现是採用单一的计数器,可是为了避免race condition(竞态条件),它也更复杂。我们来看几种不同的变体:

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

该计数器在当前秒内第一次请求被运行时创建,但它仅仅能存活一秒。假设在当前秒内,发送超过10次请求。那么该计数器将超过10。

否则它将失效并从0開始又一次计数。

在上面的代码中,存在一个race condition。假设由于某个原因。上面的代码仅仅运行了incr命令,却没有运行expire命令,那么这个key将会被泄漏,直到我们再次遇到同样的ip(备注,假设这里没有辅助的删除该key的措施,那么该key将永只是期,也将每次都错误发生,详情可见本人之前一篇文章)。

这样的问题也不难处理,能够将incr命令以及另外的expire命令打包到一个lua脚本里。该脚本能够用eval命令提交给redis运行(该方式仅仅在redis版本号大于等于2.6之后才干支持)。

local current
current = redis.call("incr",KEYS[1])
if tonumber(current) == 1 then
redis.call("expire",KEYS[1],1)
end

当然。也有还有一种方式来解决问题而不须要动用lua脚本。但须要用redis的list数据结构来替代计数器。

这样的实现方式将会更复杂。并使用更高级的特性。

但它有一个优点是记住调用当前API的每一个client的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命令仅仅在key存在时才会将值增加list

仍然须要注意的是,这里也存在一个race condition(但这却不会产生太大的影响)。问题是:exists可能返回false,但在我们运行multi/exec块内的创建list的代码之前,该list可能已被其它client创建。然而,在这个race condition发生时。将仅仅仅仅是丢失一个API调用,所以rate limiting仍然工作得非常好。

这里产生race condition不会有大问题的解决办法在于,else分支使用的rpushx,它不会导致if not than init的问题。而且expire命令将在创建list的时候以原子的形式捆绑运行。

不会产生key泄漏。导致永不失效的情况产生。

很多其它内容请訪问:http://vinoyang.com

redis实现訪问频次限制的几种方式的更多相关文章

  1. 面试中被问Spring循环依赖的三种方式!!!

    什么是循环依赖? 循环依赖其实就是循环引用,也就是两个或则两个以上的 Bean 互相持有对方,最终形成闭环.比如A依赖于B,B依赖于C,C又依赖于A.如下图: 如果在日常开发中我们用new 对象的方式 ...

  2. C++类訪问控制及继承

    一.C++类的訪问控制有三类:public,protected和private. 类訪问控制符 类成员可被哪些对象訪问 public 1.类的成员函数.2.类对象.3.友元.4.子类成员函数 prot ...

  3. JAVA设计模式之 訪问者模式【Visitor Pattern】

    一.概述 訪问者模式是一种较为复杂的行为型设计模式,它包括訪问者和被訪问元素两个主要组成部分.这些被訪问的元素通常具有不同的类型,且不同的訪问者能够对它们进行不同的訪问操作.在使用訪问者模式时,被訪问 ...

  4. java中訪问修饰符

    较之c++ 中 public,proctected, private 三种訪问控制, java多了默认訪问控制. java中四种訪问控制权限 简单描写叙述为一下四句: 1)private 仅本类可见 ...

  5. Redis面试连环问,快看看你能走到哪一步

    今天,我不自量力的面试了某大厂的java开发岗位,迎面走来一位风尘仆仆的中年男子,手里拿着屏幕还亮着的mac,他冲着我礼貌的笑了笑,然后说了句"不好意思,让你久等了",然后示意我坐 ...

  6. python訪问redis

    python訪问redis 1 Linux上安装redis a) 下载 $ wget http://download.redis.io/releases/redis-3.0.5.tar.gz b) 编 ...

  7. Linux防火墙限制指定port仅仅能由指定IP訪问

    须要对redis的端口做限制,仅仅能让公司内指定IP的机器訪问 -A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT -A ...

  8. 在nginx中使用lua直接訪问mysql和memcaced达到数据接口的统一

    安装nginx參见<nginx+lua+redis构建高并发应用> 让nginx 中的nginx_lua_module支持mysql 和memcache 下载 https://github ...

  9. juniper 550M訪问自身公网IP回流内部IP

    拓扑图示意: 网关设备juniper 550M, untrust 区: 公网地址段22.22.22.22/29 trust区:      内部员工PC地址:172.16.4.x /24 trust区: ...

随机推荐

  1. 学习笔记:delphi之TStringGrid

    1.说明 最近加入了一个项目组,使用的开发工具是delphi6,想想又要开始搞这个工具有点小忧伤,但没办法谁让咱就是个打杂的尼... 的需求是显示一个类似于Word/excel的那种表格,可以合并列等 ...

  2. JAVA 多线程随笔 (二) sleep, yield, join, wait 和notify

    这里先说明一下锁对象,如果一个类比如Person里的方法都有synchronized来修饰,那么每一个方法的锁对象就是Person的一个实例person. 锁对象也可以针对某个特定的实例, 比如syn ...

  3. Liferay 6.2 改造系列之二:清理不需要的Portlet

    一.特殊Portlet: 以下Portlet数据特殊用途的Portlet,去除后会出现运行错误: 1.站点模版 通过com.liferay.portal.events.AddDefaultLayout ...

  4. Android WebView的loadData方法注意事项

    loadData()中的html data中不能包含'#', '%', '\', '?'四中特殊字符,出现这种字符就会出现解析错误,显示找不到网页还有部分html代码.需要如何处理呢?我们需要用Url ...

  5. python KindEditord

    python 文本编辑器(KindEditord) 1.下载 官网下载:http://kindeditor.net/down.php 本地下载:http://files.cnblogs.com/fil ...

  6. [物理学与PDEs]第1章第7节 媒质中的 Maxwell 方程组 7.3 媒质中电磁场量的表示

    1. 电磁能量密度 $$\bex \cfrac{1}{2}({\bf E}\cdot{\bf D}+{\bf B}\cdot{\bf H}). \eex$$ 2. 电磁能量流密度向量 $$\bex { ...

  7. 将webcam设置为网站favicon

    今天在Twitter上看到用户davywtf将webcam设置为网站favicon. 在线示例: https://wybiral.github.io/code-art/projects/tiny-mi ...

  8. 解题(ConflictPhoneNumber--冲突的电话号码)

    题目描述 移动推出的校内网短号和亲情网短号非常方便,但在某款新手机里却出现了尴尬的bug.例如,当通讯录中包含如下号码时:1.小王:6002.小李:4676543.小张:600010输入600时,手机 ...

  9. 域名到站点的负载均衡技术一览(主要是探讨一台Nginx抵御大并发的解决方案)(转)https://www.cnblogs.com/EasonJim/p/7823410.html

    一.问题域 Nginx.LVS.Keepalived.F5.DNS轮询,往往讨论的是接入层的这样几个问题: 1)可用性:任何一台机器挂了,服务受不受影响 2)扩展性:能否通过增加机器,扩充系统的性能 ...

  10. HDU-2054.A==B?(字符串简单处理)

    这道题......被我各种姿势搞死的... 本题大意:给出两个数A和B,判断A和B是否相等,对应输出YES or NO. 本题思路:本题我有两种思路,第一种是直接去除前导零和后导零然后稍加处理比较字符 ...