Skip to content

Latest commit

 

History

History
101 lines (84 loc) · 2.65 KB

固定窗口算法.md

File metadata and controls

101 lines (84 loc) · 2.65 KB

概念

使用计数器在周期内累加访问次数,当达到设定的限流值时,触发限流策略。下一个周期开始时,进行清零,重新计数。

问题

这种限流算法无法保证限流速率,因而无法保证突然激增的流量。

假如限制一个接口一分钟只能访问10次:

  • 前半分钟一个请求没有接收,后半分钟接收了10个请求。
  • 前半分钟接收到了10个请求,那么后面的半分钟就只能拒绝请求。

这样其实没有很好的达到限流的效果。

实现方法

/**
 * 计算器方式实现限流
 *
 * @author lizhifu
 * @date 2021/1/13
 */
public class CounterLimiter {
    /**
     * 初始时间
     */
    private static long start = System.currentTimeMillis();
    /**
     * 请求计数
     */
    private static AtomicInteger count = new AtomicInteger(0);
    /**
     * 限流数
     */
    private static int limit = 10;
    /**
     * 时间窗口限制
     */
    private static final int interval = 10000;
    /**
     * 获取限流锁
     * @return
     */
    public static boolean tryAcquire(){
        long now = System.currentTimeMillis();
        if(now < start + interval){
            //判断是否超过最大请求
            if (count.get() < limit) {
                count.incrementAndGet();
                return true;
            }
            return false;
        }else{
            //超时重置
            count = new AtomicInteger(0);;
            start = now;
            return true;
        }
    }
}

代码地址:spring-boot-guava-limit

分布式环境下实现

借助Linux的lua脚本实现

--lua 下标从 1 开始
-- 获取调用脚本时传入的第一个key值(用作限流的 key List)
local key = KEYS[1]
-- 获取调用脚本时传入的第1个参数值(限流大小)
local max = tonumber(ARGV[1])
-- 获取调用脚本时传入的第2个参数值(超时时间)
local ttl = tonumber(ARGV[2])

-- 获取当前流量大小
local current = tonumber(redis.call('get', key) or "0")
local next = current + 1
-- 是否超出限流
if next > max then
-- 达到限流大小 返回 0
return 0
else
-- key 中储存的数字加上指定的增量值,如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。
-- 没有达到阈值 value + 1
redis.call("INCRBY", key, 1)
-- 每次访问均重新设置过期时间,单位毫秒
redis.call("PEXPIRE", key, ttl)
-- 返回(放行)
return next
end

代码地址:spring-boot-redis-limit