驽马十驾 驽马十驾

驽马十驾,功在不舍

目录
Reids官方文档中提到的限流器实现的读后感
/  

Reids官方文档中提到的限流器实现的读后感

前言

今天因为工作需要,查看了下redis的文档,这里简单总结下看到的知识。
改篇文章可以算做这个的读后感:https://redis.io/commands/incr

第一个:利用字符串过期(4星)

需求是:每秒中某一个Ip最多允许10次请求。
其中伪代码是:

FUNCTION LIMIT_API_CALL(ip)
ts = CURRENT_UNIX_TIME() //获取当前秒的时间戳
keyname = ip+":"+ts //key为 ip+ts
current = GET(keyname) //获取当前key是否存在

//细节1:最容易遇到的现象放在前面
IF current != NULL AND current > 10 THEN
    ERROR "too many requests per second"
END

//细节2:只会遇到一次的情况放后面
IF current == NULL THEN
    MULTI //redis中开启事务
        INCR(keyname, 1) //增加1
        EXPIRE(keyname, 1) //并且设置过期时间
    EXEC //执行事务,要么执行完成,要么完全不执行
ELSE 
    INCR(keyname, 1) 
END

PERFORM_API_CALL()

上述代码我添加了注释 //后的就是注释代码!
上述细节中有几个事情需要了解

  1. 整体是使用的String类型来装载的
  2. MULTIEXEC要联合使用,其中的命令,要么一起成功,要么一起失败。
    这个实现我感觉有问题:因为这个仅仅是1秒做法,假如是1分钟或者30S,上述函数中的ts该如何处理了?
  • 假如是1分钟还要处理利用时间格式化yyyyMMddHHmm也是可以做到,但是这个存在bug啊:某一分钟的前30秒执行的请求5,等这个某一分钟正式开始后,又可以执行10个请求了。
  • 所以我觉的的他复杂了,可以将key简化为只有ip来定义不就可以了吗?【局域网多个用户,公网IP只有一个,其实也是麻烦,只有等IPV6出来可能更好】

第二个实现:利用字符串和过期(1星)

伪代码如图

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

他们的判定,其实很类似,但是在细微处有差别,所以感慨一句:逻辑真的是一个很神奇的东西啊

  1. 上述代码先判定的是否到不为空和到10次没。
  2. 接下来没有做NULL判定,而是先加1次,然后做是否等于1的判定,如果等于1那么设置过期时间。【上述代码是先做NULl判定,然后+1和过期利用事务操作】
  3. 后面逻辑很相似
    基本流程完了,我们来分析下:
  • 方法2的第2布,先加1然后在判定,然后在设置过期时间。
  • 方法1的第2步:先判定是否为NULL,然后开启事务【加1并设置过期时间】
  • 问题就在这里,假如方法2的设置过期时间失败了?那么这个Key就会一直存在啊。
  • 最好的做法是让: 【第一次Key生成和过期时间设置】 成为一个事务,共同执行

第三个实现:利用Lua脚本(4星)

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

lua脚本是原子性的,所以利用他来进行key的初始化,这样就不会有事务的问题。
上述代码是核心代码,继续弥补后如下所示:

local current
current = redis.call("incr",KEYS[1])
if tonumber(current) == 1 then
    redis.call("expire",KEYS[1],1)
end
if tonumber(current) > KEYS[2] //KEYS[2]是最大次数
return 1
else
return 0

lua的脚本不太熟悉,上面的代码不一定能执行,意思就是这个,你如果需要用,那么就将其改造好即可。

第三个实现:利用LIST集合(2星)

其实和第一个的核心原理很像,第一个是利用STRINGINCR,这个利用LISTLLEN
个人感觉第一个对内存的占用更低,因为这个还存储的具体的值,而且值应该都是一样的,坑啊!!!

FUNCTION LIMIT_API_CALL(ip)
current = LLEN(ip)
IF current > 10 THEN
    ERROR "too many requests per second"
ELSE
    IF EXISTS(ip) == FALSE //通过EXISTSIP
        MULTI //开启事务
            RPUSH(ip,ip) //key为ip value也是ip 好逗!
            EXPIRE(ip,1) //设置过去时间
        EXEC
    ELSE
        RPUSHX(ip,ip) //key为ip value也是ip 好逗!
    END
    PERFORM_API_CALL()
END

谢谢观看!

骐骥一跃,不能十步。驽马十驾,功在不舍。