固定窗口计数器
在固定时间窗口内,记录请求次数,如果超过阈值就拒绝,否则放行。
- 优点:实现简单,性能极高
- 实现方式:incr命令和expire命令
- 缺点:临界突发问题,时间窗口固定,如果时间窗口设置一分钟,次数设置为100,那么如果00:59和01:01分别来了100条请求,都能成功执行。
- 适合于对窗口平滑度要求不高的请求。
- redis 实现方式如下,实际代码要在lua语言中编写:
key1:表示业务key
count:表示限流次数
time: 窗口大小
bash
res = incr key1; #使用 INCR 命令对 Key 进行加 1 操作。如果 Key 不存在,Redis 会先初始化为 0,然后加 1,返回 1。如果 Key 已存在,直接加 1,返回当前值。
if res == 1 # 如果返回值不是 1,说明 Key 已有过期时间,无需重复设置
expire key1 time # 设置过期时间
if res > count
return 0 # 拒绝通行
else
return 1 # 允许通行
滑动窗口法
将时间窗口划分成更小的格子,记录每个格子的请求数,当前允许的请求书 = Math.max(剩余请求数+请求数*时间间隔,每单位允许最大请求数)
- 实现方式:使用zset 记录所有请求的时间戳,请求过来的时候,先删除score < 当前时间 - 时间 间隔的 请求,计算当前已存在的请求总数precount, 如果precount < 阈值 ,允许访问,并且zscore中加入一条数据,否则禁止访问
- redis 实现方式如下,实际代码要在lua语言中编写:
bash
key1:表示业务key
count:表示限流次数
time: 窗口大小
ZREMRANGEBYSCORE key1 -inf 当前时间戳 # 清理掉所有发生在'当前窗口起始时间'之前的旧请求记录。
resp = ZCARD key1 # 统计已有请求总数
if resp < count
ZADD key 当前时间戳 "uuid" # 将当前请求添加进去
EXPIRE key1 time + 5 # 设置过期时间,防止意外情况,5表示一个随机数
return 1 # 允许通行
else
return 0 # 禁止通行
令牌桶法
- 基于hashset实现,记录上一次的更新时间,和当前token数量。模拟令牌生成和令牌消耗
- redis 实现方式如下,实际代码要在lua语言中编写:
bash
capility : 桶的容量
rate: 每秒生成的速率
# 第一步,获取key,如果key不存在,设置key,key的剩余令牌数量设置为最大值,即桶的容量, 如果key存在 解析出 current_tokens (当前令牌) 和 last_refill_time (上次时间)
res = get key1
if key is null
current_tokens = capility
last_time = current_time
# 第二步,计算当前允许的令牌数量
time_passed = current_time - last_time
new_tokens = time_passed * rate
# 不能超过桶的最大值
current_token = Math.max(capility, new_tokens + current_tokens)
# 尝试消费令牌
if current_token > 1
SET key1 "{current_token - 1}: #{current_time}"
EXPIRE key1 300 # 设置过期防泄漏
return 1 # 放行
else
SET key1 "0: #{current_time}"
EXPIRE key1 300 # 设置过期防泄漏
return 0 # 不允许通行
漏桶法
- 与令牌桶"允许突发"不同,漏桶的核心思想是 "强制平滑":无论流入的水(请求)有多快,流出的水(处理请求)必须保持恒定的速率。如果水流太快导致桶满了,多余的水就会溢出(请求被拒绝)。
- redis 实现方式如下,实际代码要在lua语言中编写:
bash
capility : 桶的容量
rate: 每秒生成的速率
current_water: 当前积水量
key1:业务key
# 第一步:获取key1,如果key1 不存在,设置current_water 为0,key1的值为"0, current_time",若存在,则解析值获取当前积水量和上一次的时间戳
res = get key1
if res == null
current_water = 0;
last_time = current_time
# 第二步:计算当前时间积水量,如果积水量计算后小于0,设置成等于0
time_passed = (current_time - last_time) / 1000 (秒)
leaked_amount = time_passed * Rate (流出速率)
current_water = Math.max(0, current_water - leaked_amount)
# 第三步:尝试注入新水 (新请求),判断加入新请求后,是否会超过桶的容量。
if current_water + 1 > capility
set key1 "#{current_water, current_time}"
EXPIRE key1 300 # 设置过期防泄漏
return -1; # 请求失败
else
set key1 "#{current_water + 1, current_time}"
EXPIRE key1 300 # 设置过期防泄漏
return 0; # 请求成功