• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    Redis和Lua实现分布式限流器的方法详解

    主要是依靠 redis + lua 来实现限流器, 使用 lua 的原因是将多条命令合并在一起作为一个原子操作, 无需过多考虑并发.

    计数器模式

    原理

    计数器算法是指在一段窗口时间内允许通过的固定数量的请求, 比如10次/秒, 500次/30秒.

    如果设置的时间粒度越细, 那么限流会更平滑.

    实现

    所使用的 Lua 脚本

    -- 计数器限流
    -- 此处支持的最小单位时间是秒, 若将 expire 改成 pexpire 则可支持毫秒粒度.
    -- KEYS[1] string 限流的key
    -- ARGV[1] int  限流数
    -- ARGV[2] int  单位时间(秒)
    
    local cnt = tonumber(redis.call("incr", KEYS[1]))
    
    if (cnt == 1) then
     -- cnt 值为1说明之前不存在该值, 因此需要设置其过期时间
     redis.call("expire", KEYS[1], tonumber(ARGV[2]))
    elseif (cnt > tonumber(ARGV[1])) then
     return -1
    end 
    
    return cnt

    返回 -1 表示超过限流, 否则返回当前单位时间已通过的请求数

    key 可以但不限于以下的情况

    优点

    缺点

    注意

    场景分析

    eg. 1000/3s 的限流

    极端情况1:

    第1秒请求数 10

    第2秒请求数 10

    第3秒请求数 980

    第4秒请求数 900

    第5秒请求数 100

    第6秒请求数 0

    此时注意第3~5秒内的总请求数高达 1980

    极端情况2:

    第1秒请求数 1000

    第2秒请求数 0

    第3秒请求数 0

    此时后续的第2~3秒会出现大量拒绝请求

    令牌桶模式

    原理

    令牌桶的

    1. 桶中保存有令牌, 存在上限, 且一开始是满的
    2. 每次请求都要消耗令牌(可根据不同请求消耗不同数量的令牌)
    3. 每隔一段时间(固定速率)会往桶中放令牌

    桶的实现还分为:

    可预消费

    提前预支令牌数: 前人挖坑, 后人跳

    不可预消费

    令牌数不够直接拒绝

    实现

    此处实现的不可预消费的令牌桶, 具体Lua代码:

    -- 令牌桶限流: 不支持预消费, 初始桶是满的
    -- KEYS[1] string 限流的key
    
    -- ARGV[1] int  桶最大容量
    -- ARGV[2] int  每次添加令牌数
    -- ARGV[3] int  令牌添加间隔(秒)
    -- ARGV[4] int  当前时间戳
    
    local bucket_capacity = tonumber(ARGV[1])
    local add_token = tonumber(ARGV[2])
    local add_interval = tonumber(ARGV[3])
    local now = tonumber(ARGV[4])
    
    -- 保存上一次更新桶的时间的key
    local LAST_TIME_KEY = KEYS[1].."_time";   
    -- 获取当前桶中令牌数
    local token_cnt = redis.call("get", KEYS[1]) 
    -- 桶完全恢复需要的最大时长
    local reset_time = math.ceil(bucket_capacity / add_token) * add_interval;
    
    if token_cnt then -- 令牌桶存在
     -- 上一次更新桶的时间
     local last_time = redis.call('get', LAST_TIME_KEY)
     -- 恢复倍数
     local multiple = math.floor((now - last_time) / add_interval)
     -- 恢复令牌数
     local recovery_cnt = multiple * add_token
     -- 确保不超过桶容量
     local token_cnt = math.min(bucket_capacity, token_cnt + recovery_cnt) - 1
     
     if token_cnt  0 then
      return -1;
     end
     
     -- 重新设置过期时间, 避免key过期
     redis.call('set', KEYS[1], token_cnt, 'EX', reset_time)      
     redis.call('set', LAST_TIME_KEY, last_time + multiple * add_interval, 'EX', reset_time)
     return token_cnt
     
    else -- 令牌桶不存在
     token_cnt = bucket_capacity - 1
     -- 设置过期时间避免key一直存在
     redis.call('set', KEYS[1], token_cnt, 'EX', reset_time);
     redis.call('set', LAST_TIME_KEY, now, 'EX', reset_time + 1); 
     return token_cnt 
    end

    令牌桶的关键是以下几个参数:

    令牌桶的实现不会出现计数器模式中单位时间内双倍流量的问题.

    总结

    以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对脚本之家的支持。

    您可能感兴趣的文章:
    • springboot+redis 实现分布式限流令牌桶的示例代码
    • 基于Redis+Lua脚本实现分布式限流组件封装的方法
    • 基于Redis实现分布式应用限流的方法
    • Redis分布式限流组件设计与使用实例
    上一篇:redis适合场景八点总结
    下一篇:Redis如何优雅的删除特定前缀key
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    Redis和Lua实现分布式限流器的方法详解 Redis,和,Lua,实现,分布式,