基于redis实现限流逻辑

固定窗口计数器

在固定时间窗口内,记录请求次数,如果超过阈值就拒绝,否则放行。

  • 优点:实现简单,性能极高
  • 实现方式: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; # 请求成功
相关推荐
Micro麦可乐10 分钟前
Redis只会用来做缓存?解锁Redis非缓存的九个应用场景,90%程序员不知道的隐藏技能
数据库·redis·缓存·消息队列·分布式锁·延迟队列·布隆过滤器
21号 114 分钟前
10.Redis 缓存
数据库·redis·缓存
从零开始的-CodeNinja之路15 分钟前
【Redis】Redis 缓存应用、淘汰机制—(四)
java·redis·缓存
小红的布丁1 小时前
Redis 集群详解:主从哨兵和切片集群有什么区别
前端·数据库·redis
杰克尼1 小时前
redis(day08-Redis原理篇)
数据库·redis·php
四谎真好看2 小时前
Redis学习笔记(高级篇3)
redis·笔记·学习·学习笔记
Jul1en_2 小时前
【Redis】String 类型命令、编码方式与应用场景
数据库·redis·缓存
空太Jun4 小时前
Redis 5大核心数据类型与持久化实战
数据库·redis·缓存
却话巴山夜雨时i4 小时前
互联网大厂Java面试:从Spring Boot到Kafka的业务场景深度剖析
spring boot·redis·spring cloud·微服务·kafka·prometheus·java面试
Jul1en_5 小时前
【Redis】单线程模型
数据库·redis·缓存