基于Redis限流(固定窗口、滑动窗口、漏桶、令牌桶)(肝货!!!)

news2024/11/18 9:34:37

近期redis复习的比较多,在限流这方面发现好像之前理解的限流算法有问题,索性花了一天“带薪摸鱼”时间肝了一天,有问题可以评论区探讨。


废话不多说,正片开始

目录

  • Maven
  • 固定窗口
  • 滑动窗口算法
  • 漏桶算法
  • 令牌桶算法

Maven

有些不用的可以自行注释,注意:这里博主springboot版本为2.7.14

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.2.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
        <!--redisson-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.17.6</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>21.0</version>
        </dependency>
    </dependencies>

固定窗口

固定窗口算法实现限流其实在之前已经写过博客(基于Redis限流(aop切面+redis实现“固定窗口算法”)),这里也简单讲解下。

固定窗口算法(计数法)即是限制在指定时间内累计数量达到峰值后,触发限流条件,例如10秒内允许访问3次,当访问第4次的时候,就被限流住了,用redis在实现的话其实用的就是incr原子自增性,然后在限制时间过期达到一个时间限制的效果

核心代码

/**
 * 固定窗口算法lua
 */
public String gdckLuaScript() {
    StringBuilder lua = new StringBuilder();
    lua.append("local c");
    lua.append("\nc = redis.call('get',KEYS[1])");
    // 调用不超过最大值,则直接返回
    lua.append("\nif c and tonumber(c) > tonumber(ARGV[1]) then");
    lua.append("\nreturn c;");
    lua.append("\nend");
    // 执行计算器自加
    lua.append("\nc = redis.call('incr',KEYS[1])");
    lua.append("\nif tonumber(c) == 1 then");
    // 从第一次调用开始限流,设置对应键值的过期
    lua.append("\nredis.call('expire',KEYS[1],ARGV[2])");
    lua.append("\nend");
    lua.append("\nreturn c;");
    return lua.toString();
}

获取lua执行语句后进行填值调用

String luaScript = gdckLuaScript();
RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);
//固定窗口法
Number count = limitRedisTemplate.execute(redisScript, keys, limitCount, limitPeriod);
if (count != null && count.intValue() <= limitCount) {
    isNeedLimit = false;
}

滑动窗口算法

滑动窗口算法是在“固定窗口算法”进行的优化,固定窗口算法有个弊端,那就是限制指定时间内只能有这么多访问量,剩余全部丢弃。那对于滑动窗口算法,是将时间周期分为N个小周期,分别记录每个小周期内访问次数,并且根据时间滑动删除过期的小周期,对于删除过期的小周期这个操作,在redis中其实是采用了zset对象的做法,score控制时间窗口,只查指定时间前到现在的一个区间(窗口)的数量,随着时间的变化,窗口一直在动

核心代码

/**
 * 滑动窗口算法lua
 */
public String hdckLuaScript() {
    StringBuilder sb = new StringBuilder();
    sb.append(" local key = KEYS[1] ");
    //sb.append(" -- 限流请求数 ");
    sb.append(" local limitCount = ARGV[1] ");
    //sb.append(" -- 限流开始时间戳(一般是当前时间减去前多少范围时间,例如前5秒) ");
    sb.append(" local startTime = ARGV[2] ");
    //sb.append(" -- 限流结束时间戳(当前时间) ");
    sb.append(" local endTime = ARGV[3] ");
    //sb.append(" -- 限流超时时间-用于清除内存-毫秒(默认与限制时间一致) ");
    sb.append(" local timeout = ARGV[4] ");
    //当前请求数
    sb.append(" local currentCount = redis.call('zcount', key, startTime, endTime)  ");
    //sb.append(" -- 限流存在并且超过限流大小,则返回剩余可用请求数=0 ");
    sb.append(" if (currentCount and tonumber(currentCount) >= tonumber(limitCount)) then ");
    sb.append("     return 0 ");
    sb.append(" end ");
    //sb.append(" -- 记录本次请求 ");
    sb.append(" redis.call('zadd', key, endTime, endTime) ");
    //sb.append(" -- 设置超时时间 ");
    sb.append(" redis.call('expire', key, timeout) ");
    //sb.append(" -- 返回剩余可用请求数 ");
    sb.append(" return tonumber(limitCount) - tonumber(currentCount) ");
    return sb.toString();
}

获取lua执行语句后进行填值调用

String luaScript = hdckLuaScript();
RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);
long currentMillis = System.currentTimeMillis();
//限制时间区间毫秒
int limitPeriodHm = limitPeriod * 1000;
//之前的时间戳(用于框定窗口滑动,(之前时间到当前时间))
long beforeMillis = currentMillis - limitPeriodHm;
//滑动窗口算法
Number count = limitRedisTemplate.execute(redisScript, keys, limitCount, beforeMillis, currentMillis,limitPeriod);
if (count != null && count.intValue() > 0){
    isNeedLimit = false;
}

漏桶算法

漏桶算法的思路是访问请求到达时直接放入漏桶,如当前容量已达到上限(限流值),则进行丢弃(触发限流策略)。漏桶以固定的速率进行释放访问请求(即请求通过),直到漏桶为空。

核心代码

/**
 * 漏桶算法lua
 */
public String ltLuaScript(){
    StringBuilder sb = new StringBuilder();
    //sb.append(" --参数说明:key[1]为对应服务接口的信息,capacity为容量,passRate为漏水速率,addWater为每次请求加水量(默认为1),water为当前水量,lastTs为时间戳 ");
    sb.append(" local limitInfo = redis.call('hmget', KEYS[1], 'capacity', 'passRate','water', 'lastTs') ");
    sb.append(" local capacity = limitInfo[1] ");
    sb.append(" local passRate = limitInfo[2] ");
    //加水量固定为1(一次请求)
    sb.append(" local addWater= 1 ");
    sb.append(" local water = limitInfo[3] ");
    sb.append(" local lastTs = limitInfo[4] ");
    //sb.append(" --初始化漏斗 ");
    sb.append(" if capacity == false or passRate == false then ");
    sb.append("     capacity = tonumber(ARGV[1]) ");
    sb.append("     passRate = tonumber(ARGV[2]) ");
    //sb.append("     --当前水量(第一次加水量) ");
    sb.append("     water = addWater ");
    sb.append("     lastTs = tonumber(ARGV[3]) ");
    sb.append("     redis.call('hmset', KEYS[1], 'capacity', capacity, 'passRate', passRate,'addWater',addWater,'water', water, 'lastTs', lastTs) ");
    sb.append("     return 1 ");
    sb.append(" else ");
    sb.append("     local nowTs = tonumber(ARGV[3]) ");
    //sb.append("     --计算距离上一次请求到现在的漏水量 ");
    sb.append("     local waterPass = tonumber((nowTs - lastTs)* passRate/1000) ");
    //sb.append("     --计算当前水量,即执行漏水 ");
    sb.append("     water=math.max(0,water-waterPass) ");
    //sb.append("     --设置本次请求的时间 ");
    sb.append("     lastTs = nowTs ");
    //sb.append("     --判断是否可以加水 ");
    sb.append("     addWater=tonumber(addWater) ");
    sb.append("     if capacity-water >= addWater then ");
    //sb.append("         --加水 ");
    sb.append("         water=water+addWater ");
    //sb.append("         --更新当前水量和时间戳 ");
    sb.append("         redis.call('hmset', KEYS[1], 'water', water, 'lastTs', lastTs) ");
    sb.append("         return 1 ");
    sb.append("     end ");
    sb.append("     return 0 ");
    sb.append(" end ");
    return sb.toString();
}

获取lua执行语句后进行填值调用

long currentMillis = System.currentTimeMillis();
String luaScript = ltLuaScript();
RedisScript<Number>redisScript = new DefaultRedisScript<>(luaScript, Number.class);
//漏桶算法
//漏水速率(这里用的是平均速率,也可以自定义)
double passRate = limitCount / (double) limitPeriod;
//注意注意,currentMillis、passRate千万不要转字符串,会报错。。。
Number count = limitRedisTemplate.execute(redisScript, keys, limitCount, passRate, currentMillis);
if (count != null && count.intValue() > 0){//此处count为1正常加水,0加水失败即限流
    isNeedLimit = false;
}

令牌桶算法

令牌桶算法是程序以r(r=时间周期/限流值)的速度向令牌桶中增加令牌,直到令牌桶满,请求到达时向令牌桶请求令牌,如获取到令牌则通过请求,否则触发限流策略,跟漏桶有点像,不过漏桶算法是请求方是加水(自动漏水),而令牌桶算法是减少“水”(自动加“水”)。

核心代码

/**
 * 令牌桶算法lua
 */
public String lptLuaScript(){
    StringBuilder sb = new StringBuilder();
    //sb.append(" --参数说明:key[1]为对应服务接口的信息,capacity为最大容量,rate为令牌生成速率(例如500ms生成一个则为0.5),leftTokenNum为剩余令牌数,lastTs为时间戳 ");
    sb.append(" local limitInfo = redis.call('hmget', KEYS[1], 'capacity', 'rate','leftTokenNum', 'lastTs') ");
    sb.append(" local capacity = limitInfo[1] ");
    sb.append(" local rate = limitInfo[2] ");
    sb.append(" local leftTokenNum= limitInfo[3] ");
    sb.append(" local lastTs = limitInfo[4] ");
    // 本次需要令牌数
    sb.append(" local need = 1 ");
    //sb.append(" --初始化令牌桶 ");
    sb.append(" if capacity == false or rate == false or leftTokenNum == false then ");
    sb.append("     capacity = tonumber(ARGV[1]) ");
    sb.append("     rate = tonumber(ARGV[2]) ");
    sb.append("     leftTokenNum = tonumber(ARGV[1]) - need ");
    sb.append("     lastTs = tonumber(ARGV[3]) ");
    sb.append("     redis.call('hmset', KEYS[1], 'capacity', capacity, 'rate', rate, 'leftTokenNum', leftTokenNum, 'lastTs', lastTs) ");
    sb.append("     return leftTokenNum ");
    sb.append(" else ");
    sb.append(" 	local nowTs = tonumber(ARGV[3]) ");
//        sb.append("     rate = tonumber(ARGV[2])");
    //sb.append("     --计算距离上一次请求到现在生产令牌数 ");
    sb.append("     local createTokenNum = tonumber((nowTs - lastTs)* rate/1000) ");
    //sb.append(" 	--计算该段时间的剩余令牌(当前总令牌数) ");
    sb.append("     leftTokenNum = createTokenNum + leftTokenNum ");
    //sb.append(" 	--设置剩余令牌(留下最小数) ");
    sb.append("     leftTokenNum = math.min(capacity, leftTokenNum) ");
    //sb.append(" 	--设置本次请求的时间 ");
    sb.append("     lastTs = nowTs ");
    //sb.append("     --判断是否还有令牌 ");
    sb.append("     if leftTokenNum >= need then ");
    //sb.append("         --减去需要的令牌 ");
    sb.append("         leftTokenNum = leftTokenNum - need ");
    //sb.append("         --更新剩余空间和上一次的生成令牌时间戳 ");
    sb.append("         redis.call('hmset', KEYS[1], 'capacity', capacity, 'rate', rate,'leftTokenNum', leftTokenNum, 'lastTs', lastTs) ");
    sb.append("         return leftTokenNum ");
    sb.append("     end ");
    sb.append("     return -1 ");
    sb.append(" end ");
    return sb.toString();
}

获取lua执行语句后进行填值调用

long currentMillis = System.currentTimeMillis();
long luaScript = lptLuaScript();
RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);
//令牌桶算法
//生成令牌速率(这里用的是平均速率,也可以自定义)
double createRate = limitCount / (double) limitPeriod;
count = limitRedisTemplate.execute(redisScript, keys, limitCount, createRate, currentMillis);
if (count != null && count.intValue() >= 0){
    isNeedLimit = false;
}

由于代码量过大,放置在博主资源啦,核心部分均已贴出
调用整体示例如图在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1466984.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[HTML]Web前端开发技术28(HTML5、CSS3、JavaScript )JavaScript基础——喵喵画网页

希望你开心&#xff0c;希望你健康&#xff0c;希望你幸福&#xff0c;希望你点赞&#xff01; 最后的最后&#xff0c;关注喵&#xff0c;关注喵&#xff0c;关注喵&#xff0c;佬佬会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的…

【计网】TCP的三次握手四次挥手

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;JAVA ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 三次握手&#xff08;Connection Establishment&#xff09; 四次挥手&#xff08;Connection Termination&#xff09; 结语 我…

Echarts与后台(mongoose)交互

Echarts引入地址可参考 echarts组件引入 <template><div><div id"main" style"width: 600px;height:400px;"></div></div> </template><script setup> import { onMounted, ref } from vue; import * as echa…

JavaScript 数组、遍历

数组 多维数组&#xff1a;数组里面嵌套 一层数组为二维数组。一维数组的使用频率是最高的。 如果数组访问越界会返回undefined。 数组遍历 数组方法Array.isArray() 这个方法可以去判定一个内容是否是数组。

32单片机基础:GPIO输出

目录 简介&#xff1a; GPIO输出的八种模式 STM32的GPIO工作方式 GPIO支持4种输入模式&#xff1a; GPIO支持4种输出模式&#xff1a; 浮空输入模式 上拉输入模式 下拉输入模式 模拟输入模式&#xff1a; 开漏输出模式&#xff1a;&#xff08;PMOS无效&#xff0c;就…

基于SpringBoot的气象数据监测分析大屏

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

简单聊聊如何零基础入门机器视觉

在这个充满科技奇迹的时代里&#xff0c;机器视觉像一面魔法镜&#xff0c;赋予机器以“视觉”&#xff0c;让它们能够理解和解释这个世界。对于热爱探索新知的大学生和研究生们来说&#xff0c;学习机器视觉不仅是开启未来科技大门的钥匙&#xff0c;更是一次激动人心的冒险之…

RisingWave最佳实践-利用Dynamic filters 和 Temporal filters 实现监控告警

心得的体会 刚过了年刚开工&#xff0c;闲暇之余调研了分布式SQL流处理数据库–RisingWave&#xff0c;本人是Flink&#xff08;包括FlinkSQL和Flink DataStream API&#xff09;的资深用户&#xff0c;但接触到RisingWave令我眼前一亮&#xff0c;并且拿我们生产上的监控告警…

备战蓝桥杯————双指针技巧巧解数组1

利用双指针技巧来解决七道与数组相关的题目。 两数之和 II - 输入有序数组&#xff1a; 给定一个按升序排列的数组&#xff0c;找到两个数使它们的和等于目标值。可以使用双指针技巧&#xff0c;在数组两端设置左右指针&#xff0c;根据两数之和与目标值的大小关系移动指针。 …

动态规划--持续更新篇

将数字变成0的操作次数 1.题目 2.思路 在numberOfSteps函数中&#xff0c;首先设置f[0]为0&#xff0c;因为0已经是0了&#xff0c;不需要任何步骤。然后&#xff0c;使用一个for循环从1迭代到输入的整数num。对于每个整数i&#xff0c;如果i是奇数&#xff0c;则将f[i]设置为…

uniapp离线打包(使用Android studio打包)

一、准备工作 安装HbuilderX&#xff0c;记住版本号下载对应HbuilderX版本的Android离线SDK&#xff0c;如我使用3.6.18版本打包&#xff0c;则对应应下载3.6.18版本的SDK&#xff08;官网不提供旧版本的SDK&#xff0c;有些需要自己找&#xff09;官网下载地址&#xff1a;ht…

目标检测-Transformer-ViT和DETR

文章目录 前言一、ViT应用和结论结构及创新点 二、DETR应用和结论结构及创新点 总结 前言 随着Transformer爆火以来&#xff0c;NLP领域迎来了大模型时代&#xff0c;成为AI目前最先进和火爆的领域&#xff0c;介于Transformer的先进性&#xff0c;基于Transformer架构的CV模型…

QGIS编译(跨平台编译)之七十:【Windows编译错误处理】找不到vector_tile.pb.h、vector_tile.pb.cc

文章目录 一、错误描述二、错误原因分析三、错误处理一、错误描述 ①无法打开源文件“vector_tile.pb.h” ②无法打开包含文件:“vector_tile.pb.h”:No Such file or directory ③无法打开源文件:“vector_tile.pb.cc”:No Such file or directory 二、错误原因分析 qgis\…

MFC 配置Halcon

1.新建一个MFC 工程&#xff0c;Halcon 为64位&#xff0c;所以先将工程改为x64 > VC 目录设置包含目录和库目录 包含目录 库目录 c/c ->常规 链接器 ->常规 > 链接器输入 在窗口中添加头文件 #include "HalconCpp.h" #include "Halcon.h"…

Oracle迁移到mysql-表结构的坑

1.mysql中id自增字段必须是整数类型 id BIGINT AUTO_INCREMENT not null, 2.VARCHAR2改为VARCHAR 3.NUMBER(16)改为decimal(16,0) 4.date改为datetime 5.mysql范围分区必须int格式&#xff0c;不能list类型 ERROR 1697 (HY000): VALUES value for partition …

应急响应实战笔记03权限维持篇(3)

0x00 前言 攻击者在获取服务器权限后&#xff0c;会通过一些技巧来隐藏自己的踪迹和后门文件&#xff0c;本文介绍Linux下的几种隐藏技术。 0x01 隐藏文件 Linux 下创建一个隐藏文件&#xff1a;touch .test.txt touch 命令可以创建一个文件&#xff0c;文件名前面加一个 点…

使用pygame 编写俄罗斯方块游戏

项目地址&#xff1a;https://gitee.com/wyu_001/mypygame/tree/master/game 可执行程序 这个游戏主要使用pygame库编写俄罗斯方块游戏&#xff0c;demo主要演示了pygame开发游戏的主要设计方法和实现代码 下面是游戏界面截图 游戏主界面&#xff1a; 直接上代码&#xff1a…

RabbitMQ 部署方式选择

部署模式 RabbitMQ支持多种部署模式&#xff0c;可以根据应用的需求和规模选择适合的模式。以下是一些常见的RabbitMQ部署模式&#xff1a; 单节点模式&#xff1a; 最简单的部署方式&#xff0c;所有的RabbitMQ组件&#xff08;消息存储、交换机、队列等&#xff09;都运行在…

Redis可视化工具——RedisInsight

文章目录 1. 下载2. 安装3. RedisInsight 添加 Redis 数据库4. RedisInsight 使用 RedisInsight 是 Redis 官方出品的可视化管理工具&#xff0c;支持 String、Hash、Set、List、JSON 等多种数据类型的管理&#xff0c;同时集成了 RedisCli&#xff0c;可进行终端交互。 1. 下载…

数组与指针相关

二级指针与指针数组 #include <stdio.h> #include <stdlib.h> int main() { // 定义一个指针数组&#xff0c;每个元素都是一个指向int的指针 int *ptr_array[3]; // 为指针数组的每个元素分配内存 ptr_array[0] malloc(2*sizeof(int)); ptr_array[1] m…