zoukankan      html  css  js  c++  java
  • 限流算法实践

    https://mp.weixin.qq.com/s/HCo2ILhq2-Psc5nwcfvD5g

    限流算法实践

    yzy 360技术 2021-04-23
     

    假如我们的限流策略是一分钟内最多能通过600个请求,那么相应的令牌产生速率为 600 / 60 = 10 (个/秒) 。那么当限流策略刚刚配置好这一时刻就有突发的10个请求进来,此时令牌桶内还没来的及生产令牌,所以请求拿不到令牌就会被拒绝,这显然不符合我们要求。

    为了解决这一问题,我们在限流策略刚刚配置好后的第一个请求来临时将当前可用令牌的值设置为桶的最大容量 600,将最近一次请求时间设置为本次请求来临时一分钟后的时间戳,减去出本次请求需要的令牌后更新桶。这样,在这一分钟以内,有下一次请求进来时,从 Hash 表内取出配置计算当前时间就会小于最近一次请求的时间,随后计算生成的令牌就会是一个小于0的负数。所以在更新桶这一步,要根据生成的令牌是否为负数来决定是否更新最后一次请求时间的值。

    用 Lua 脚本实现上述逻辑:

    local key = KEYS[1] -- 要进行限流的Key,可以是 urilocal consume_permits = tonumber(ARGV[1]) -- 请求消耗的令牌数,每个请求消耗一个local curr_time = tonumber(ARGV[2]) -- 当前时间
    local limiter_info = redis.pcall("HMGET", key, "last_time", "curr_permits", "bucket_cap", "rate", "period")if not limiter_info[3] then return -1endlocal last_time = tonumber(limiter_info[1]) or 0local curr_permits = tonumber(limiter_info[2]) or 0local bucket_cap = tonumber(limiter_info[3]) or 0local rate = tonumber(limiter_info[4]) or 0local period = tonumber(limiter_info[5]) or 0
    local total_permits = bucket_caplocal is_update_time = trueif last_time > 0 then local new_permits = math.floor((curr_time-last_time)/1000 * rate) if new_permits <= 0 then new_permits = 0 is_update_time = false end
    total_permits = new_permits + curr_permits if total_permits > bucket_cap then total_permits = bucket_cap endelse last_time = curr_time + period * 1000end
    local res = 1if total_permits >= consume_permits then total_permits = total_permits - consume_permitselse res = 0end
    if is_update_time then redis.pcall("HMSET", key, "curr_permits", total_permits, "last_time", curr_time)else redis.pcall("HSET", key, "curr_permits", total_permits)endreturn res

    上述脚本在调用时接收三个参数,分别为:限流的key、请求消耗的令牌数、 当前时间戳(毫秒级别)。

    在我们的业务代码中,先调用 Redis 的 SCRIPT LOAD 命令将上述脚本 Load 到 Redis 中并将该命令返回的脚本 sha1 值保存。

    在后续的请求进来时,调用 Redis 的 EVALSHA 命令执行限流逻辑,根据返回值判断是否对本次请求触发限流行为。假如限流的 key 为每次请求的 uri,每次请求消耗 1 个令牌,那么执行 Evalsha 命令进行限流判断的具体操作为:EVALSHA ${sha1} 1 ${uri} 1 ${当前时间戳} (第一个数字 1 代表脚本可接收的参数中有 1 个Key,第二个数字 1 代表本次请求消耗一个令牌);执行完这条命令后如果返回值是 1 代表桶中令牌够用,请求通过;如果返回值为 0 代表桶中令牌不够,触发限流;如果返回值为 -1 代表本次请求的 uri 未配置限流策略,可根据自己的实际业务场景判断是通过还是拒绝。

    5

    总结

    本文主要介绍了四种限流的算法,分别为:固定窗口计数器算法、滑动窗口计数算法、漏桶算法、令牌桶算法。

    • 固定窗口计数算法简单易实现,其缺陷是可能在中间的某一秒内通过的请求数是限流阈值的两倍,该算法仅适用于对限流准确度要求不高的应用场景。

    • 滑动窗口计数算法解决了固定窗口计数算法的缺陷,但是该算法较难实现,因为要记录每次请求所以可能出现比较占用内存比较多的情况。

    • 漏桶算法可以做到均匀平滑的限制请求,Ngixn 热 limit_req 模块也是采用此种算法。因为匀速处理请求的缘故所以该算法应对限流阈值内的突发请求无法及时处理。

    • 令牌桶算法解决了以上三个算法的所有缺陷,是一种相对比较完美的限流算法,也是限流场景中应用最为广泛的算法。使用 Redis + Lua脚本的方式可以简单的实现。

    参考链接

    https://www.nginx.com/blog/rate-limiting-nginx/

    https://www.infoq.cn/article/qg2tx8fyw5vt-f3hh673https://segmentfault.com/a/1190000019676878

    https://en.wikipedia.org/wiki/Token_bucket

     
     
     
     
  • 相关阅读:
    [crontab]修改默认编辑器
    [mysql]忘记用户密码或者误删用户账号
    [vim]多行注释和多行删除
    [mysql]my.cnf在哪里
    [python]有中文字符程序异常的解决方案
    [Linux]虚拟机无法安装deepin15.9的解决方案
    Elasticsearch5.X IN Windows 10 系列文章(2)
    Elasticsearch5.X IN Windows 10 系列文章(1)
    HTTP Error 502.5
    centos7 yum install redis
  • 原文地址:https://www.cnblogs.com/rsapaper/p/14696019.html
Copyright © 2011-2022 走看看