当前位置: 首页 > news >正文

Redis + Lua 实现分布式限流器

文章目录

    • Redis + Lua 限流实现
      • 1. 导入依赖
      • 2. 配置application.properties
      • 3. 配置RedisTemplate实例
      • 4. 定义限流类型枚举类
      • 5. 自定义注解
      • 6. 切面代码实现
      • 7. 控制层实现
      • 8. 测试

相比 Redis事务, Lua脚本的优点:

  • 减少网络开销:使用Lua脚本,无需向Redis 发送多次请求,执行一次即可,减少网络传输
  • 原子操作:Redis 将整个Lua脚本作为一个命令执行,原子,无需担心并发
  • 复用:Lua脚本一旦执行,会永久保存 Redis 中,,其他客户端可复用

Redis + Lua 限流实现

技术栈:自定义注解aopRedis + Lua 实现限流

1. 导入依赖

 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30.1-jre</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

2. 配置application.properties

spring.redis.host=10.1.61.121
spring.redis.port=6379
spring.redis.password=123456

3. 配置RedisTemplate实例

package com.lihw.lihwtestboot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.io.Serializable;@Configuration
public class RedisLimiterHelper {@Beanpublic RedisTemplate<String, Serializable> limitRedisTemplate(LettuceConnectionFactory redisConnectionFactory) {RedisTemplate<String, Serializable> template = new RedisTemplate<>();template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new GenericJackson2JsonRedisSerializer());template.setConnectionFactory(redisConnectionFactory);return template;}
}

4. 定义限流类型枚举类

package com.lihw.lihwtestboot.schemas;/*** @explain: 限流类型* @author: lihewei
*/
public enum LimitType {/*** 自定义key*/CUSTOMER,/*** 请求者IP*/IP;
}

5. 自定义注解

  • period表示请求限制时间段
  • count表示在period这个时间段内允许放行请求的次数。
  • limitType代表限流的类型,可以根据请求的IP自定义key,如果不传limitType属性则默认用方法名作为默认key。
package com.lihw.lihwtestboot.anno;
import com.lihw.lihwtestboot.schemas.LimitType;
import java.lang.annotation.*;/*** @explain: 自定义限流注解* @author: lihewei
*/
@Target({ElementType.METHOD, ElementType.TYPE})//作用于方法上
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Limit {/*** 名字*/String name() default "";/*** key*/String key() default "";/*** Key的前缀*/String prefix() default "";/*** 给定的时间范围 单位(秒)*/int period();/*** 一定时间内最多访问次数*/int count();/*** 限流的类型(用户自定义key 或者 请求ip)*/LimitType limitType() default LimitType.CUSTOMER;
}

6. 切面代码实现

package com.lihw.lihwtestboot.aop;
import com.google.common.collect.ImmutableList;
import com.lihw.lihwtestboot.anno.Limit;
import com.lihw.lihwtestboot.schemas.LimitType;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.lang.reflect.Method;/*** @explain: 限流切面实现* @author: lihewei
*/
@Aspect
@Configuration
public class LimitInterceptor {private static final Logger logger = LoggerFactory.getLogger(LimitInterceptor.class);private static final String UNKNOWN = "unknown";private final RedisTemplate<String, Serializable> limitRedisTemplate;@Autowiredpublic LimitInterceptor(RedisTemplate<String, Serializable> limitRedisTemplate) {this.limitRedisTemplate = limitRedisTemplate;}/*** @author lihw* @description 切面*/@Around("execution(public * *(..)) && @annotation(com.lihw.lihwtestboot.anno.Limit)")public Object interceptor(ProceedingJoinPoint pjp) {MethodSignature signature = (MethodSignature) pjp.getSignature();Method method = signature.getMethod();Limit limitAnnotation = method.getAnnotation(Limit.class);LimitType limitType = limitAnnotation.limitType();String name = limitAnnotation.name();String key;int limitPeriod = limitAnnotation.period();int limitCount = limitAnnotation.count();/*** 根据限流类型获取不同的key ,如果不传我们会以方法名作为key*/switch (limitType) {case IP:key = getIpAddress();break;case CUSTOMER:key = limitAnnotation.key();break;default:key = StringUtils.upperCase(method.getName());}ImmutableList<String> keys = ImmutableList.of(StringUtils.join(limitAnnotation.prefix(), key));try {String luaScript = buildLuaScript();RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);Number count = limitRedisTemplate.execute(redisScript, keys, limitCount, limitPeriod);logger.info("Access try count is {} for name={} and key = {}", count, name, key);if (count != null && count.intValue() <= limitCount) {return pjp.proceed();} else {throw new RuntimeException("You have been dragged into the blacklist");}} catch (Throwable e) {if (e instanceof RuntimeException) {throw new RuntimeException(e.getLocalizedMessage());}throw new RuntimeException("server exception");}}/*** @description 编写 redis Lua 限流脚本*/public String buildLuaScript() {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();}/*** @description 获取id地址*/public String getIpAddress() {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();String ip = request.getHeader("x-forwarded-for");if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return ip;}
}

7. 控制层实现

package com.lihw.lihwtestboot.controller;import com.lihw.lihwtestboot.anno.Limit;
import com.lihw.lihwtestboot.schemas.LimitType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.atomic.AtomicInteger;@RestController
public class LimiterController {private static final AtomicInteger ATOMIC_INTEGER_1 = new AtomicInteger();private static final AtomicInteger ATOMIC_INTEGER_2 = new AtomicInteger();private static final AtomicInteger ATOMIC_INTEGER_3 = new AtomicInteger();@Limit(key = "limitTest", period = 10, count = 3)@GetMapping("/limitTest1")public int testLimiter1() {return ATOMIC_INTEGER_1.incrementAndGet();}@Limit(key = "customer_limit_test", period = 10, count = 3, limitType = LimitType.CUSTOMER)@GetMapping("/limitTest2")public int testLimiter2() {return ATOMIC_INTEGER_2.incrementAndGet();}@Limit(key = "ip_limit_test", period = 10, count = 3, limitType = LimitType.IP)@GetMapping("/limitTest3")public int testLimiter3() {return ATOMIC_INTEGER_3.incrementAndGet();}
}

8. 测试

10s内连续请求三次以上拒绝请求

相关文章:

Redis + Lua 实现分布式限流器

文章目录 Redis Lua 限流实现1. 导入依赖2. 配置application.properties3. 配置RedisTemplate实例4. 定义限流类型枚举类5. 自定义注解6. 切面代码实现7. 控制层实现8. 测试 相比 Redis事务&#xff0c; Lua脚本的优点&#xff1a; 减少网络开销&#xff1a;使用Lua脚本&…...

ruoyi若依框架SpringSecurity实现分析

系列文章 ruoyi若依框架学习笔记-01 ruoyi若依框架分页实现分析 ruoyi若依框架SpringSecurity实现分析 文章目录 系列文章前言具体分析一、项目中的SpringSecurity版本二、登录认证流程分析三、权限鉴定四、退出登录五、SpringSecurity配置类 总结 前言 在ruoyi-vue若依框…...

Habitat环境学习四:Habitat-sim基础用于导航——使用导航网格NavMesh

如何使用导航网格NavMesh 官方教程1、NavMesh基础定义1.1 使用NavMesh的原因1.2 什么是NavMesh 2、NavMesh的使用方法2.1 获取自上而下Top down view视角地图2.2 在NavMesh中进行查询以及随机产生可导航点2.3 查找最短路径2.4 场景加载NavMesh2.5 重新计算并生成NavMesh2.6 什么…...

python学习笔记 -- 字符串

目录 一、输出字符串的格式 二、字符串的一些函数 1、len函数&#xff1a;字符串长度 2、查找字符所在位置index 3、某字符在字符串中的个数count 4、字符切片 对字符串进行翻转 -- 利用步长 5、修改大小写字母&#xff1a; 6、判断开头和结尾 7、拆分字符串 一、输出…...

2024年GPT如何发展?

2023 年&#xff0c;人工智能领域最具影响的莫过于 GPT-4、ChatGPT 了。 ChatGPT 凭一己之力掀起了 AI 领域的热潮&#xff0c;火爆全球&#xff0c;似乎开启了第四次工业革命。 ChatGPT 入选《Nature》2023 年度十大人物&#xff08;Nature’s 10&#xff09;&#xff0c;这…...

从REPR设计模式看 .NET的新生代类库FastEndpoints的威力

📢欢迎点赞 :👍 收藏 ⭐留言 📝 如有错误敬请指正,赐人玫瑰,手留余香!📢本文作者:由webmote 原创📢作者格言:新的征程,我们面对的不仅仅是技术还有人心,人心不可测,海水不可量,唯有技术,才是深沉黑夜中的一座闪烁的灯塔 !序言 又到了一年年末,春节将至…...

前端入门:(五)JavaScript 续

10. 浏览器存储 10.1 Cookie的概念和使用 Cookie是一种存储在用户计算机上的小型文本文件&#xff0c;用于跟踪和识别用户。Cookie通常用于存储用户的偏好设置、会话信息等&#xff0c;可以通过JavaScript进行读取和设置。 // 示例&#xff1a;设置和读取Cookie document.co…...

研究多态恶意软件,探讨网络安全与AI

前言 近期ChatGPT火遍全球&#xff0c;AI技术被应用到了全球各行各业当中&#xff0c;国内外各大厂商也开始推出自己的ChatGPT&#xff0c;笔者所在公司在前段时间也推出了自研的安全GPT&#xff0c;AI技术在网络安全行业得到了很多的应用&#xff0c;不管是网络安全研究人员、…...

linux驱动工作原理

linux或者windows驱动是如何对上和对下工作的&#xff0c;请用中文回答 在Linux系统中&#xff0c;设备驱动程序通过在/dev目录下创建文件系统条目与硬件通信。应用程序通过打开这些文件来获取描述符&#xff0c;以此来与设备交互。驱动程序内部使用主次设备号来标识设备。而在…...

Rust语言入门(第3篇)

引用与借用 上一篇中&#xff0c;我们介绍了rust的所有权概念&#xff0c;若直接传递变量做函数参数&#xff0c;堆上的变量就会失去所有权&#xff0c;而栈上变量则由于复制&#xff0c;仍有所有权。 fn main(){let b 3;makes_copy(b);println!("after using a variab…...

电脑服务器离线安装.net framework 3.5解决方案(错误:0x8024402c )(如何确定当前系统是否安装NET Framework 3.5)

问题环境&#xff1a; 日常服务的搭建或多或少都会有需要到NET Framework 3.5的微软程序运行框架&#xff0c;本次介绍几种不同的安装方式主要解决运行在Windows 2012 以上的操作系统的服务。 NET Framework 3.5 是什么&#xff1f; .NET Framework是微软公司推出的程序运行框架…...

Three.js学习8:基础贴图

一、贴图 贴图&#xff08;Texture Mapping&#xff09;&#xff0c;也翻译为纹理映射&#xff0c;“贴图”这个翻译更直观。 贴图&#xff0c;就是把图片贴在 3D 物体材质的表面&#xff0c;让它具有一定的纹理&#xff0c;来为 3D 物体添加细节的一种方法。这使我们能够添加…...

【Linux】进程学习(二):进程状态

目录 1.进程状态1.1 阻塞1.2 挂起 2. 进程状态2.1 运行状态-R进一步理解运行状态 2.2 睡眠状态-S2.3 休眠状态-D2.4 暂停状态-T2.5 僵尸状态-Z僵尸进程的危害 2.6 死亡状态-X2.7 孤儿进程 1.进程状态 1.1 阻塞 阻塞&#xff1a;进程因为等待某种条件就绪&#xff0c;而导致的…...

Spring Boot 笔记 003 Bean注册

使用Idea导入第三方jar包 在porn.xml种添加的第三方jar包依赖&#xff0c;并刷新 可以在启动类中尝试调用 以上放到启动类中&#xff0c;不推荐&#xff0c;建议创建一个专门定义的类 package com.geji.config;import cn.itcast.pojo.Country; import cn.itcast.pojo.Province;…...

PCIE 参考时钟架构

一、PCIe架构组件 首先先看下PCIE架构组件&#xff0c;下图中主要包括&#xff1a; ROOT COMPLEX (RC) (CPU); PCIE PCI/PCI-X Bridge; PCIE SWITCH; PCIE ENDPOINT (EP) (pcie设备); BUFFER; 各个器件的时钟来源都是由100MHz经过Buffer后提供。一个PCIE树上最多可以有256…...

【开源】JAVA+Vue.js实现在线课程教学系统

目录 一、摘要1.1 系统介绍1.2 项目录屏 二、研究内容2.1 课程类型管理模块2.2 课程管理模块2.3 课时管理模块2.4 课程交互模块2.5 系统基础模块 三、系统设计3.1 用例设计3.2 数据库设计 四、系统展示4.1 管理后台4.2 用户网页 五、样例代码5.1 新增课程类型5.2 网站登录5.3 课…...

计算机网络(第六版)复习提纲29

第六章&#xff1a;应用层 SS6.1 域名系统DNS 1 DNS被设计为一个联机分布式数据库系统&#xff0c;并采用客户服务器方式&#xff08;C/S&#xff09; 2 域名的体系结构 3 域名服务器及其体系结构 A 域名服务器的分类 1 根域名服务器 2 顶级域名服务器&#xff08;TLD服务器&a…...

有道ai写作,突破免费限制,无限制使用

预览效果 文末提供源码包及apk下载地址 有道ai写作python版 import hashlib import time import json import ssl import base64 import uuidfrom urllib.parse import quote import requests from requests_toolbelt.multipart.encoder import MultipartEncoder from Crypto…...

node.js 使用 elementtree 生成思维导图 Freemind 文件

请参阅: java : pdfbox 读取 PDF文件内书签 请注意&#xff1a;书的目录.txt 编码&#xff1a;UTF-8&#xff0c;推荐用 Notepad 转换编码。 npm install elementtree --save 编写 txt_etree_mm.js 如下 // 读目录.txt文件&#xff0c;使用 elementtree 生成思维导图 Free…...

Vue中路由守卫的详细应用

作为一名web前端开发者&#xff0c;我们肯定经常使用Vue框架来构建我们的项目。而在Vue中&#xff0c;路由是非常重要的一部分&#xff0c;它能够实现页面的跳转和导航&#xff0c;提供更好的用户体验。然而&#xff0c;有时我们需要在路由跳转前或跳转后执行一些特定的逻辑&am…...

golang循环变量捕获问题​​

在 Go 语言中&#xff0c;当在循环中启动协程&#xff08;goroutine&#xff09;时&#xff0c;如果在协程闭包中直接引用循环变量&#xff0c;可能会遇到一个常见的陷阱 - ​​循环变量捕获问题​​。让我详细解释一下&#xff1a; 问题背景 看这个代码片段&#xff1a; fo…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命

在华东塑料包装行业面临限塑令深度调整的背景下&#xff0c;江苏艾立泰以一场跨国资源接力的创新实践&#xff0c;重新定义了绿色供应链的边界。 跨国回收网络&#xff1a;废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点&#xff0c;将海外废弃包装箱通过标准…...

ffmpeg(四):滤镜命令

FFmpeg 的滤镜命令是用于音视频处理中的强大工具&#xff0c;可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下&#xff1a; ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜&#xff1a; ffmpeg…...

MySQL用户和授权

开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务&#xff1a; test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容

目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法&#xff0c;当前调用一个医疗行业的AI识别算法后返回…...

蓝桥杯3498 01串的熵

问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798&#xff0c; 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…...

九天毕昇深度学习平台 | 如何安装库?

pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子&#xff1a; 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...