Spring Boot通过自定义注解和Redis+Lua脚本实现接口限流
😄 19年之后由于某些原因断更了三年,23年重新扬帆起航,推出更多优质博文,希望大家多多支持~
🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
🎐 个人CSND主页——Micro麦可乐的博客
🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
🌺《RabbitMQ》专栏主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程
🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~
Spring Boot通过自定义注解和Redis+Lua脚本实现接口限流
- 前言
- 操作思路
- 这么做有什么优势
- 开始实现
- ❶ 项目初始化
- ❷ 创建限流注解
- ❸ 创建Lua脚本
- ❹ 创建Redis处理器
- ❺ 编写限流切面
- ❻ 编写Controller
- 接口测试
- 总结
前言
本文源码下载地址:https://download.csdn.net/download/lhmyy521125/89412365
在我们日常开发的项目中为了保证系统的稳定性,很多时候我们需要对系统接口做限流处理,它可以有效防止恶意请求对系统造成过载。常见的限流方案主要有:
- 网关限流:
NGINX
、Zuul
等 API 网关- 服务器端限流:服务端接口限流
- 令牌桶算法:通过定期生成令牌放入桶中,请求需要消耗令牌才能通过
- 熔断机制:
Hystrix
、Resilience4j
等
之前博主写过了一篇 【使用Spring Boot自定义注解 + AOP实现基于IP的接口限流和黑白名单】,在一些小型应用中,足以满足我们的需求,但是在并发量大的时候,就会有点力不从心,本章节博主将给大家介绍
使用自定义注解和 Redis
+Lua
脚本实现接口限流
操作思路
使用redis
Redis
是一种高性能的键值存储系统,支持多种数据结构。由于其高吞吐量和低延迟的特点,Redis
非常适合用于限流
应用Lua脚本
Lua
脚本可以在Redis
中原子执行多条命令。通过在Redis
中执行Lua
脚本,可以确保限流操作的原子性和一致性
限流策略
本文我们将采用类似令牌桶算法(Token Bucket
)来实现限流
令牌桶算法的基本思想:系统会以固定的速率向桶中加入令牌,每次请求都需要消耗一个令牌,当桶中没有令牌时,拒绝请求
这么做有什么优势
高效性
Redis
以其高性能著称,每秒可以处理数十万次操作。使用Redis进行限流,确保了在高并发场景下的高效性。同时,Lua
脚本在Redis
中的执行是原子
的,这意味着脚本中的一系列命令要么全部执行,要么全部不执行,避免了竞争条件,确保了限流逻辑的一致性
灵活性
通过自定义注解
,我们可以为不同的接口设置不同的限流策略,而不需要修改大量的代码。这种方法允许开发者根据实际需求灵活地调整限流参数,例如每秒允许的请求数和令牌的有效期,从而更好地应对不同的业务场景
易于维护和扩展
使用Spring AOP
和注解,可以方便地将限流逻辑应用于不同的接口。这种方式不仅减少了代码的耦合度,还使得限流逻辑的维护和扩展变得更加简单。例如,当需要为某个新的接口添加限流时,只需在方法上添加相应的注解即可,而不需要在代码中加入复杂的限流逻辑
分布式限流
Redis
作为一个分布式缓存系统,可以方便地部署在集群环境中,实现分布式限流
。通过将限流数据存储在Redis
中,可以在多个应用实例之间共享限流状态,确保在分布式环境下限流策略的一致性
开始实现
❶ 项目初始化
首先,创建一个 Spring Boot
项目,并添加必要的依赖。在 pom.xml
文件中添加以下内容:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
</dependencies>
配置 application.yml
加入 redis
配置
spring:#redisredis:# 地址host: 127.0.0.1# 端口,默认为6379port: 6379# 数据库索引database: 0# 密码password:# 连接超时时间timeout: 10slettuce:pool:# 连接池中的最小空闲连接min-idle: 0# 连接池中的最大空闲连接max-idle: 8# 连接池的最大数据库连接数max-active: 8# #连接池最大阻塞等待时间(使用负值表示没有限制)max-wait: -1ms
❷ 创建限流注解
定义一个自定义注解RateLimit
,主要有三个属性 限流的key
、允许的请求数
、令牌有效期
import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {String key() default ""; // 限流的keyint limit() default 10; // 每秒允许的请求数int timeout() default 1; // 令牌有效期(秒)
}
❸ 创建Lua脚本
编写一个Lua
脚本,用于限流操作:
-- rate_limit.lua
-- 获取限流的键(标识符)
local key = KEYS[1]
-- 获取每秒允许的最大请求数
local limit = tonumber(ARGV[1])
-- 获取键的过期时间(秒)
local expire_time = tonumber(ARGV[2])-- 获取当前的请求数
local current = redis.call('get', key)
-- 如果当前请求数存在且已经超过或达到限制,返回0(拒绝请求)
if current and tonumber(current) >= limit thenreturn 0
else-- 如果当前请求数不存在或未超过限制,增加请求数current = redis.call('incr', key)-- 如果这是第一次请求,设置过期时间if tonumber(current) == 1 thenredis.call('expire', key, expire_time)end-- 返回1(允许请求)return 1
end
脚本工作原理总结
- 每次请求进来时,脚本会首先获取当前的请求数。
- 如果请求数已经达到设定的限制,则拒绝该请求。
- 否则,增加请求数,并在首次请求时设置过期时间。
- 返回结果表示是否允许请求。
❹ 创建Redis处理器
创建Redis
处理器,用于执行Lua
脚本:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Component;import java.util.Collections;@Component
public class RedisRateLimitHandler {@Autowiredprivate StringRedisTemplate redisTemplate;private DefaultRedisScript<Long> redisScript;public RedisRateLimitHandler() {redisScript = new DefaultRedisScript<>();redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("rate_limit.lua")));redisScript.setResultType(Long.class);}public boolean isAllowed(String key, int limit, int expireTime) {Long result = redisTemplate.execute(redisScript, Collections.singletonList(key), String.valueOf(limit), String.valueOf(expireTime));return result != null && result == 1;}
}
❺ 编写限流切面
使用 AOP
实现限流逻辑,IP判断
、模拟用户判断
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;@Aspect
@Component
public class RateLimitAspect {@Autowiredprivate RedisRateLimitHandler redisRateLimitHandler;@Autowiredprivate HttpServletRequest request;@Around("@annotation(rateLimit)")public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {String key = rateLimit.key();int limit = rateLimit.limit();int expireTime = rateLimit.timeout();switch (key) {case LimitTypeConstants.IP://获取IP地址key = request.getRemoteAddr();break;case LimitTypeConstants.USER:/*** 模拟当前获取当前用户限流配置 比如高级会员 1小时允许请求多少次普通会员允许多少次* key = user.token;* limit = user.user.token;* expireTime = 3600 //1小时;*/key = "user-token";break;default:key = rateLimit.key();break;}boolean allowed = redisRateLimitHandler.isAllowed(key, limit, expireTime);if (allowed) {return joinPoint.proceed();} else {throw new RuntimeException("请求太多-超出速率限制");}}
}
❻ 编写Controller
创建一个简单的限流测试Controller,并在需要限流的方法上使用 @RateLimit
注解,需要编写异常处理,返回 RateLimitAspect
异常信息,并以字符串形式返回
import com.toher.lua.limit.LimitTypeConstants;
import com.toher.lua.limit.RateLimit;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RequestMapping("/api")
@RestController
public class RedisLuaController {//由于是简单的测试项目,这里就直接定义异常处理,并为采用全局异常处理@ExceptionHandler(value = Exception.class)public String handleException(Exception ex) {return ex.getMessage();}@GetMapping("/limit-ip")@RateLimit(key = LimitTypeConstants.IP, limit = 5, timeout = 30)public String rateLimitIp() {return "IP Request successful!";}@GetMapping("/limit-export")@RateLimit(key = LimitTypeConstants.USER, limit = 5, timeout = 30)public String rateLimitUser(){return "USER Request successful!";}@GetMapping("/limit")@RateLimit(key = "customer", limit = 5, timeout = 30)public String rateLimit(){return "customer Request successful!";}
}
接口测试
使用接口调试工具,请求接口测试,博主这里使用的是 Apifox
,我们30秒内请求5次
前5次均返回 Request successful!
第6次会提示 请求太多-超出速率限制
总结
通过本文的步骤,我们成功地在Spring Boot
项目中结合Redis
和Lua
脚本实现了一个灵活高效的接口限流功能。通过自定义注解
和AOP
切面,可以方便地为不同的接口设置不同的限流策略。
如果本文对您有所帮助,希望 一键三连 给博主一点点鼓励,如果您有任何疑问或建议,请随时留言讨论。
相关文章:
Spring Boot通过自定义注解和Redis+Lua脚本实现接口限流
😄 19年之后由于某些原因断更了三年,23年重新扬帆起航,推出更多优质博文,希望大家多多支持~ 🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志 🎐 个人CSND主页——Mi…...
硬件工程师的蜗牛成长路
一名合格的硬件工程师,需要掌握的知识有很多,知识点积累不是一蹴而就,而是细水长流,螺旋提升,不急,慢慢来,想掌握的都能掌握,就让时间来见证个人的成长路径。 ---大青山 2024/6/10 …...
简单记录玩4399游戏flash插件问题
一、因谷歌浏览器默认禁止flash插件自动运行,所以玩家在使用谷歌浏览器,访问www.4399.com平台页面或者4399小游戏(flash资源)时,可能会出现加载异常的情况。今天教大家如何开启flash插件 二、下载falsh官方插件 地址:Flash Player官方下载中心-Flash中国官网 三、如果您…...
GNU/Linux - 使用字符设备来操作GPIO
从 4.8 版开始,Linux 内核引入了基于字符设备的新用户空间 API,用于管理和控制 GPIO(通用输入/输出)。这篇文章介绍了新接口的基本原理,并通过一个简单的教程/示例演示了如何使用新 API 控制 GPIO。 教程中使用的硬件是…...
Android13 Settings 左上角箭头图标点击无效
最近在修改A311D2方案固件,系统Settings发现很多bug 最明显的是左上角有个箭头样子的图标,通常认为是返回键,点击之后没有任何效果,目测这个是ActionBar的按键。 SettingsBaseActivity里面有一段这样的代码: // Th…...
WinForms 应用(.NET 8.0)使用ReportViewerCore.WinForms显示打印RDLC报表
在要WinForms 应用(.NET 8.0)中,显示RDLC报表,就要使用ReportViewerCore.WinForms。原来的ReportViewer只能在.NET Framework框架下运行。 1.ReportViewerCore.WinForms 程序包说明 SQL Server Reporting Services ReportViewer…...
【网络安全】【深度学习】【入侵检测】SDN模拟网络入侵攻击并检测,实时检测,深度学习
文章目录 1. 前言2. Mininet 和 Ryu 的区别2.1 Mininet2.2 Ryu2.3 总结 3. 模拟攻击3.1 环境准备3.2 创建 Mininet 网络拓扑3.2 启动 Ryu 控制器3.3 模拟网络攻击3.4 捕获流量 4. 实时异常检测4.1 在 Ryu 控制器中4.2 在 h2 机器上的实验结果4.3 深度学习模型部署上h2机器 帮助…...
【CentOS】手动编译安装make、cmake、gcc、git
摘要 Centos7升级make和gcc版本到最新——CSDN make make 各个版本下载地址 http://ftp.gnu.org/pub/gnu/make 以4.4为例安装: # 下载 wget https://ftp.gnu.org/pub/gnu/make/make-4.4.tar.gz # 解压配置 tar zxf make-4.4.tar.gz cd make-4.4 ./configure --p…...
45.django - 开始建立第一个项目
1.django是什么? Django是一个高级的、免费的、开源的Web应用框架,它由Python编程语言编写而成。Django遵循模型-视图-控制器(MVC)的设计模式,但通常将其称为模型-视图-模板(MVT)架构。它的主要…...
# 梯影传媒T6投影仪刷机方法及一些刷机工具链接
梯影传媒T6投影仪刷机方法及一些刷机工具链接 文章目录 梯影传媒T6投影仪刷机方法及一些刷机工具链接1、安装驱动程序2、备份设备rom【boot、system】3、还原我要刷进设备的rom【system】4、打开开发者模式以便于安装apk5、root设备6、更多好链接: 梯影传媒T6使用的…...
【代码随想录算法训练营第37期 第三十二天 | LeetCode122.买卖股票的最佳时机II、55. 跳跃游戏、45.跳跃游戏II】
代码随想录算法训练营第37期 第三十二天 | LeetCode122.买卖股票的最佳时机II、55. 跳跃游戏、45.跳跃游戏II 一、122.买卖股票的最佳时机II 解题代码C: class Solution { public:int maxProfit(vector<int>& prices) {int result 0;for(int i 1; i &…...
DP:回文串模型
一、回文子串 . - 力扣(LeetCode) 该题有3种解法 (1)中心扩展算法(在字符串章节有介绍)时间复杂度O(N^2),空间复杂度O(1) (2)马丁车…...
STM32CubeMX软件的安装以及配置
STM32CubeMX软件的配置过程可以详细分为以下几个步骤,以确保您能够顺利地使用该软件进行STM32微控制器的配置和代码生成: 1. 安装前准备 安装JAVA环境:由于STM32CubeMX软件是基于JAVA环境运行的,所以需要先安装Java Runtime Env…...
【适配鸿蒙next】Flutter 新一代混合栈管理框架
前言 据最新消息显示,华为今年下半年将全面转向其自主平台HarmonyOS,放弃Android系统。 报道中提到,下一版HarmonyOS预计将随华为即将推出的Mate 70旗舰系列一起发布。 据悉,HarmonyOS Next 已经扩展到4000个应用程序,…...
车载电子电气架构 --- 车载信息安全
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…...
【数据结构(邓俊辉)学习笔记】图04——双连通域分解
文章目录 0. 概述1 关节点与双连通域2 蛮力算法3 可行算法4 实现5 示例6 复杂度 0. 概述 学习下双连通域分解,这里略微有一点点难,这个算是DFS算法的非常非常经典的应用,解决的问题也非常非常有用。 1 关节点与双连通域 连通性很好理解&am…...
UI学习(二)
UI学习(二) 文章目录 UI学习(二)布局子视图手动布局自动布局 导航控制器导航控制器基础导航控制器的切换导航栏工具栏 分栏控制器分栏控制器协议部分的内容UITableView基础部分相关的协议函数高级协议与单元格 多界面传值 布局子视…...
【嵌入式】波特率9600,发送8个字节需要多少时间,如何计算?
问题: 波特率9600,发送 01 03 00 00 00 04 44 09 (8字节) 需要多少时间,如何计算? 在计算发送数据的时间时,首先要考虑波特率以及每个字符的数据格式。对于波特率9600和标准的UART数据格式(1个起始位&…...
jmeter -n -t 使用非GUI模式运行脚本说明
命令模式下执行jmx文件 jmeter -n -t fatie.jmx -l results\t4.jtl -e -o results\h1 表示以命令行模式运行当前目录下的脚本fatie.jmx,将结果存入当前目录下的results\t1.jtl,并且生成html格式的报告,写入文件夹results\h1。 说明:生成结果的文件夹r…...
网络流媒体协议——HLS协议
HTTP 实时流媒体(HTTP Live Streaming,HLS)协议是苹果公司提出的主要用于直播的流媒体协议。一个完整的基于HLS协议的流媒体直播系统由四部分组成,即音视频采集器、媒体服务器、媒体分发器和播放客户端。 媒体服务器 媒体服务器的…...
Linux服务器扩容及磁盘分区(LVM和非LVM)
Linux扩容及磁盘分区(LVM和非LVM) 本文主要介绍了阿里云服务器centos的扩容方法:非LVM分区扩容方法(系统盘),以及磁盘改LVM并分区(数据盘)。主要是ext4文件系统及xfs磁盘scsi MBR分…...
支持向量机
支持向量机(SVM) 支持向量机(Support Vector Machine, SVM)是一种用于分类和回归任务的监督学习算法。SVM 的核心思想是找到一个最优的决策边界(或称为超平面),以最大化不同类别之间的间隔。以…...
Kafka 架构
1 整体架构 1.1 Zookeeper Zookeeper 是一个分布式协调服务,用于管理 Kafka 的元数据。它负责维护 Kafka 集群的配置信息、Broker 列表和分区的 Leader 信息。 Zookeeper 确保了 Kafka 集群的高可用性和可靠性。 但 Zookeeper 已经成为 Kafka 性能瓶颈,…...
iOS 查看runtime源码的几种方法
目录 前言 查看runtime 源码方法 1.下载 Apple 官方提供的源代码 2.通过 GitHub 访问镜像 3.使用命令行工具查看 4.示例 前言 这篇博客主要介绍了查看iOS runtime源代码的方法。 查看runtime 源码方法 查看iOS runtime源码的方法包括以下几个步骤: 1.下载 A…...
底板外设倒灌到处理器分析
在嵌入式系统中,底板外设通常与处理器通过各种接口(如UART、SPI、I2C、GPIO等)进行连接。这些外设可能包括传感器、执行器、存储器、通信模块等。倒灌是指当外设向处理器提供的信号电平超出了处理器能够接受的范围,导致处理器无法…...
使用贝塞尔曲线实现一个iOS时间轴
UI效果 实现的思路 就是通过贝塞尔曲线画出时间轴的圆环的路径,然后 使用CAShaper来渲染UI,再通过 animation.beginTime [cilrclLayer convertTime:CACurrentMediaTime() fromLayer:nil] circleTimeOffset 来设置每个圆环的动画开始时间, …...
【深度学习】深度学习之巅:在 CentOS 7 上打造完美Python 3.10 与 PyTorch 2.3.0 环境
【深度学习】深度学习之巅:在 CentOS 7 上打造完美Python 3.10 与 PyTorch 2.3.0 环境 大家好 我是寸铁👊 总结了一篇【深度学习】深度学习之巅:在 CentOS 7 上打造完美Python 3.10 与 PyTorch 2.3.0 环境✨ 喜欢的小伙伴可以点点关注 &#…...
在docker容器中使用gdb调试python3.11的进程
gdb调试python进程的前提条件 安装python及python调试信息安装gdb工具安装python-gdb.py扩展 安装过程 我们使用docker来安装以上内容,Dockerfile文件内容如下: FROM docker.io/centos:7.4.1708# 安装依赖 RUN yum install -y -q epel-release &…...
堆排序要点和难点以及具体案例应用
堆排序(Heap Sort)是一种基于堆数据结构的排序算法。下面我将以分点表示和归纳的方式,结合相关数字和信息,详细描述堆排序的PTA(Programming and Testing Approach,编程与测试方法)。 1. 堆排序原理 堆排序是一种树形选择排序,利用了完全二叉树的性质,通过构建最大堆…...
pyspark中使用mysql jdbc报错java.lang.ClassNotFoundException: com.mysql.jdbc.Driver解决
报错信息: py4j.protocol.Py4JJavaError: An error occurred while calling o33.load. : java.lang.ClassNotFoundException: com.mysql.jdbc.Driver 我的解决方法: 这个报错就是提示你找不到jar包,所以你需要去下载一个和你mysql版本匹配的j…...
wordpress 修改登录页面/搜索引擎优化的主题
当许多人为网站选择1、 系统资源的不足在服务器上一样时可以安装软件的,如果软件的数量过多的话,就是会占本来就不多的空间了,所以我们要做的就是对于一些没有用的程序可以选择卸载或者关闭使用的状态。2、 病毒木马的危害这个的话就不用解释…...
网站中文域名好不好/seo石家庄
1. 需求 目前我们开发的TCP服务端程序只能服务于一个客户端,如何开发一个多任务版的TCP服务端程序能够服务于多个客户端呢? 完成多任务,可以使用线程,比进程更加节省内存资源。 2. 具体实现步骤 编写一个TCP服务端程序,循环等…...
济南seo网站优化/免费优化网站排名
坑人的引用类型。。。。输出看看结果是什么 Queue<ParaInfo> strStranList new Queue<ParaInfo>();StringBuilder strSql new StringBuilder();strSql.Append("Update Jia72_Members Set ");strSql.Append("UserNameUserName,");strSql.Appe…...
保险做的好的网站有哪些内容/网站关键词优化网站推广
今天要做的是获取UG安装目录中的后处理文件,后处理文件以“*.pui”为后缀。这里我要做的就是批量获取UG安装目录中符合后缀名的文件名称,然后将这些名称提供给UG对话框进行显示。获取UG安装路径用户可能把UG安装在任何目录,所以没法指定固定…...
关于网站建设的建议/韶关新闻最新今日头条
想象一下,当程序所有的业务逻辑都完成的时候,你可能还来不及喘口气,紧张的测试即将来临。你的Boss告诉你,虽然程序没问题,但某些方法为什么执行这么慢,性能堪忧。领会了Boss的意图之后,漫长的排…...
枣庄哪里有做网站设计/网络推广精准营销推广
在 Java 中调用 Go 的大致过程如下go --> cgo --> jna --> java整个过程要解决的问题主要两个:数据类型在两种语言中如何转化何时清理无用的数据下面就围绕上述调用过程来阐述,本文涉及代码完整版可以下面链接找到:Go -> Cgo这是…...