zoukankan      html  css  js  c++  java
  • redis实现訪问频次限制的几种方式

    结合上一篇文章《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

  • 相关阅读:
    sklearn: TfidfVectorizer 中文处理及一些使用参数
    sklearn: TfidfVectorizer 中文处理及一些使用参数
    python在文件中输入整数
    python在文件中输入整数
    Python文件操作,with open as追加文本内容实例
    Python文件操作,with open as追加文本内容实例
    Python中的Bunch模式
    Python中的Bunch模式
    python文本挖掘模版
    python文本挖掘模版
  • 原文地址:https://www.cnblogs.com/gavanwanggw/p/7240675.html
Copyright © 2011-2022 走看看