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

近期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;
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

B树的介绍

R-B Tree 简介特性B树特性m阶B树的性质&#xff08;这些性质是B树规定的&#xff09; B树的搜索B树的添加B树的删除——非叶子结点 简介 R-B Tree又称为Red-Black Tree&#xff0c;红黑树。是一种特殊的二叉查找树&#xff0c;红黑树的每个节点上都有存储为表示结点的颜色&…

3-Bean的生命周期

Bean的生命周期 创建对象和初始化是在AbstractAutowireCapableBeanFactory.doCreateBean()中完成的 创建对象 实例化&#xff08;构造方法&#xff09;依赖注入 初始化 执行Aware接口回调执行BeanPostProcessor.postProcessBeforeInitialization执行InitializingBean回调&…

ShardingSphere 5.x 系列【15】分布式主键生成器

有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot 版本 3.1.0 本系列ShardingSphere 版本 5.4.0 源码地址:https://gitee.com/pearl-organization/study-sharding-sphere-demo 文章目录 1. 概述2. 配置3. 内置算法3.1 UUID3.2 Snowflake3.3 NanoId3.4 CosId3.5 Co…

PostgreSQL教程(四):高级特性

一、简介 在之前的章节里我们已经涉及了使用SQL在PostgreSQL中存储和访问数据的基础知识。现在我们将要讨论SQL中一些更高级的特性&#xff0c;这些特性有助于简化管理和防止数据丢失或损坏。最后&#xff0c;我们还将介绍一些PostgreSQL扩展。 本章有时将引用教程&#xff0…

Rust之构建命令行程序(四):用TDD(测试-驱动-开发)模式来开发库的功能

开发环境 Windows 11Rust 1.75.0 VS Code 1.86.2 项目工程 这次创建了新的工程minigrep. 用测试-驱动模式来开发库的功能 既然我们已经将逻辑提取到src/lib.rs中&#xff0c;并将参数收集和错误处理留在src/main.rs中&#xff0c;那么为代码的核心功能编写测试就容易多了。我…

Opencv实战(2)绘图与图像操作

Opencv实战(2)绘图与图像操作 指路前文&#xff1a;Opencv实战(1)读取与像素操作 三、基本绘图 文章目录 Opencv实战(2)绘图与图像操作三、基本绘图(1).line(2).rectangle(3).circle 四、图像处理(1).颜色空间1.意义2.cvtColor()3.inRange()4.适应光线 (2).形态操作1.腐蚀2.膨…

【软件架构】01-架构的概述

1、定义 软件架构就是软件的顶层结构 RUP&#xff08;统一过程开发&#xff09;4 1 视图 1&#xff09;逻辑视图&#xff1a; 描述系统的功能、组件和它们之间的关系。它主要关注系统的静态结构&#xff0c;包括类、接口、包、模块等&#xff0c;并用于表示系统的组织结构…

unity hub (第一部)初学配置

1、安装Unity Hub 2、设置中文 3、安装编辑器 4、新建项目 5、新建完成后进入编辑器 6、 编辑器设置中文 editPreferencesLanguages选择中文

mysql的日志文件在哪?

阅读本文之前请参阅----MySQL 数据库安装教程详解&#xff08;linux系统和windows系统&#xff09; MySQL的日志文件通常包括错误日志、查询日志、慢查询日志和二进制日志等。这些日志文件的位置取决于MySQL的安装和配置。以下是一些常见的日志文件位置和如何找到它们&#xff…

Android Studio基础(下载安装与简单使用)

1、搭建Android开发平台 1.1 Android Studio 下载地址及版本说明 Android 开发者官网&#xff1a; https://developer.android.com/index.html&#xff08;全球&#xff0c;需科学上网&#xff09; https://developer.android.google.cn/index.html&#xff08;国内&#xff…

【Flink精讲】Flink任务调度机制

Graph 的概念 Flink 中的执行图可以分成四层&#xff1a; StreamGraph -> JobGraph -> ExecutionGraph -> 物理执 行图。 StreamGraph&#xff1a;是根据用户通过 Stream API 编写的代码生成的最初的图。用来表示程序的拓扑结构。JobGraph&#xff1a; StreamGraph …

Spring篇----第三篇

系列文章目录 文章目录 系列文章目录前言一、使用 Spring 有哪些方式?二、什么是 Spring IOC 容器?三、什么是依赖注入?四、可以通过多少种方式完成依赖注入?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这…

【MATLAB】 EWT信号分解+FFT傅里叶频谱变换组合算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~ 展示出图效果 1 EWT分解算法 EWT分解算法是一种基于小波变换的信号分解算法&#xff0c;它可以将信号分解为一系列具有不同频率特性的小波分量。该算法的基本思想是将信号分解为多个不同尺度的小波分量&#xff0c;并对…

了解网络延迟-MDN文档学习笔记

了解延迟 查看更多学习笔记&#xff1a;GitHub&#xff1a;LoveEmiliaForever MDN中文官网 CDN CDN (内容分发网络) 指的是一组分布在各个地区的服务器 这些服务器存储着数据的副本&#xff0c;因此服务器可以根据哪些服务器与用户距离最近&#xff0c;来满足数据的请求 CD…

BUUCTF crypto做题记录(8)新手向

一、密码学心声 得到信息如下图 背景故事没什么信息&#xff0c;主要看曲谱。大概率不会让我们涉及与音乐有关的内容&#xff0c;题目中也提示说答案是一串字符串&#xff0c;所以我们可以猜测是将曲谱上的数字转化成字符。曲谱中文字提示是用ASCII码进行转换。没有数字8可能是…

iOS面试:4.多线程GCD

一、多线程基础知识 1.1 什么是进程&#xff1f; 进程是指在系统中正在运行的一个应用程序。对于电脑而已&#xff0c;你打开一个软件&#xff0c;就相当于开启了一个进程。对于手机而已&#xff0c;你打开了一个APP&#xff0c;就相当于开启了一个进程。 1.2 什么是线程&am…

Java核心-核心类与API(2)

话接上回&#xff0c;继续核心类与API的学习&#xff0c;这次介绍StringBuffer/StringBuilder/StringJoiner类。StringBuffer和StringBuilder是我们学习的重点&#xff0c;建议对比学习&#xff0c;做好区分。 一、StringBuffer类 1、概述 1&#xff09;问题 由于 String 类…

Django学习记录04——靓号管理整合

1.靓号表 1.1 表结构 1.2 靓号表的构造 class PrettyNum(models.Model): 靓号表 mobile models.CharField(verbose_name"手机号", max_length11)# default 默认值# null true&#xff0c;blank true 允许为空price models.IntegerField(verbose_name"价…

websocket了解下

websocket请求长啥样 GET /chat HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ Sec-WebSocket-Version: 13 啥是websocket websocket是http的一种&#xff0c;服务器可以主动向客户端推送信息&#xff0c;…

18个惊艳的可视化大屏(第七辑):场馆与园区方向

本期分享智慧场馆和智慧园区方向的可视化大屏&#xff0c;各位老铁上车&#xff0c;坐稳了&#xff0c;上图啦。