使用计数器在周期内累加访问次数,当达到设定的限流值时,触发限流策略。下一个周期开始时,进行清零,重新计数。
这种限流算法无法保证限流速率,因而无法保证突然激增的流量。
假如限制一个接口一分钟只能访问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;
}
}
}
借助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