springboot-aop-redis-lua 实现的分布式限流方案
1.自定义限流注解
 @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;
}
 
 
2. 限流类型
 public enum LimitType {/*** 自定义key*/CUSTOMER,/*** 请求者IP*/IP;
} 
 
3.redis配置
 @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. 限流切面实现
 /*** @author* @description 限流切面实现* @date 2020/4/8 13:04*/
@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;}/*** @param pjp* @authofuor * @description 切面* @date 2020/4/8 13:04*/@Around("execution(public * *(..)) && @annotation(com..limit.api.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");}}/*** @author xiaofu* @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;");logger.info("====="+lua.toString());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;}
} 
 
上述Lua脚本解释:jdk8在书写脚本的时候需要使用字符串拼接的方式,jdk17以及以后可以使用代码片段书写
local c //局部变量
c = redis.call('get',KEYS[1]) //从redis获取key执行的次数
if c and tonumber(c) > tonumber(ARGV[1]) then //获取的key执行的次数是否大于允许的最大值,如果是直接返回,拒绝访问
return c;
end
c = redis.call('incr',KEYS[1]) //key对应的value增加1
if tonumber(c) == 1 then //当前的key是否是第一次
redis.call('expire',KEYS[1],ARGV[2]) //对key设置过期时间
end
return c;
5.测试例子
 package com.xiaofu.limit.controller;import com.xiaofu.limit.api.Limit;
import com.xiaofu.limit.enmu.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(name = "1111", 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();}} 
相关文章:
springboot-aop-redis-lua 实现的分布式限流方案
1.自定义限流注解 Target({ElementType.METHOD, ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) Inherited Documented public interface Limit {/*** 名字*/String name() default "";/*** key*/String key() default "";/*** Key的前缀*/String…...
C++ realloc()用法及代码示例
C realloc()用法及代码示例 C 中的realloc() 函数重新分配先前分配但尚未释放的内存块。realloc() 函数重新分配先前使用 malloc() 、 calloc() 或 realloc() 函数分配但尚未使用 free() 函数释放的内存。如果新大小为零,则返回的值取决于库的实现。它可能会也可能…...
【Go】gin框架生成压缩包与下载文件
在没有加入下面这串代码之前,下载的压缩包一直为空。遂debug了两个小时。。。 可以在服务端本地创建压缩包。单独将服务端本地的压缩包发送给客户端也是没问题的。但是两个合起来,客户端接收到的压缩包内容就为空了。 期间也尝试了 zipFile.Close() zipW…...
iOS 面试题以及自我理解答案
1、简述push原理,push的证书和其他的有什么不一样? 第 一阶段:BeejiveIM服务器把要发送的消息、目的iPhone的标识打包,发给APNS。 第二阶段:APNS在自身的已注册Push服务 的iPhone列表中,查找有相应标识的iP…...
vue实现自定义滚动条
vue实现自定义滚动条 具体效果如下,这边我用的rem单位,比例是1:40, 先写下页面布局,把原生的滚动条给隐藏掉,给自定义的滑块增加transition: marginLeft 1s linear;可以使左边距过度的更顺滑 .top-box-2::-webkit-scr…...
基于Qt C++的工具箱项目源码,含命令行工具、桌面宠物、文献翻译、文件处理工具、医学图像浏览器、插件市场、设置扩展等工具
一、介绍 1. 基本信息 完整代码下载地址:基于Qt C的工具箱项目源码 TBox是一款基于Qt C的工具箱。用户可以自行选择安装所需的工具(以插件的形式),将TBox打造成专属于自己的效率软件。TBox基本界面展示如下: 2. 使用…...
C# AnimeGANv2 人像动漫化
效果 项目 下载 可执行程序exe下载 源码下载 其他 C# 人像卡通化 Onnx photo2cartoon-CSDN博客...
gateway接口参数加解密
上篇介绍了多种加解密的使用java加密使用 本篇主要介绍在gateway网关中使用对参数解密和返回数据进行加密的操作 原理 下面使用的是AES加密 SHA1withRSA加签 1-用户使用拿到的AES秘钥和RSA私钥。对数据进行加密和加签 2-进行验签和时间的检验 3-将解密的数据返回到具体的调用…...
WorkPlus定制化的局域网会议软件,提供安全稳定的会议体验
在现代商业环境中,迅速而高效的沟通是企业成功的关键要素之一。而在传统的会议模式下,时间成本和地理限制往往给企业带来不小的困扰。针对这一问题,WorkPlus推出了一款创新的局域网会议软件——WorkPlus Meet,旨在为企业创造高效的…...
干货|小白也能自制电子相册赶紧码住~
你是否想拥有一个独一无二的电子相册,却又苦于不知道如何下手?今天教你一个简单的方法,即使你是小白,也能轻松自制电子相册! 一、选择合适的工具 首先,你需要选择一个合适的工具来制作电子相册。有很多工具…...
docker之Harbor私有仓库
目录 一、什么是Harbor 二、Harbor的特性 三、Harbor的构成 1、六个组件 2、七个容器 四、私有镜像仓库的上传与下载 五、部署docker-compose服务 把项目中的镜像数据进行打包持久数据,如镜像,数据库等在宿主机的/data/目录下, 一、什么…...
服务器上部署python脚本
1.查看服务器上的python是否自带,一般都自带 2.将本地脚本上传到服务器 3.直接运行一下脚本看报什么错误 代码错误, 将f删除后报别的错误 上面是未安装依赖的错误。我们安装一下依赖 下面是编码的解决 #!/usr/bin/python # -*- coding: utf-8 -*- 先把…...
【excel技巧】如何在Excel表格中添加选项按钮?
不知道大家是否会9遇到需要勾中选项的情况,我们可以在电子表格中制作出可以勾选、选中的选项按钮,今天我们一起学习一下设置方法。 首先,我们需要先在excel工具栏中添加一个功能模块:开发工具 依次点击excel中的文件 – 选项 –…...
前端 vite+vue3——写一个随机抽奖组件
文章目录 ⭐前言⭐设计布局⭐交互设计⭐整体代码⭐insicode代码 ⭐总结⭐结束 ⭐前言 大家好,我是yma16,本文分享关于前端 vitevue3——写一个抽奖随机组件。 vue3系列相关文章: 前端vue2、vue3去掉url路由“ # ”号——nginx配置 csdn新星计…...
语音芯片基础知识 什么是语音芯 他有什么作用 发展趋势是什么
目录 一、语音芯片的简介 常见的语音芯片有哪些? 语音芯片的种类有很多,大体区分下来也就4个类别而已: 选型的经验说明如下: 推荐使用flash型语音芯片 一、语音芯片的简介 语音芯片基础知识: 什么是语音芯片&…...
设计模式01———简单工厂模式 c#
首先我们打开一个项目 在这个初始界面我们需要做一些准备工作 建基础通用包 创建一个Plane 重置后 缩放100倍 加一个颜色 任务:使用【简单工厂模式】生成四种不同怪物 【按不同路径移动】 首先资源商店下载四个怪物模型 接下来我们选取四个怪物作为预制体并分别起名…...
如何解决MidJourney错过付费后被暂停
问题 假定你已经成功订阅购买了 MidJourney 一段时间,下个月扣费周期到了。 如果你卡里余额不足,卡被封或失效了,或者你想着最近没啥用得上 MidJourney 的地方先省着不续费,等要用的时候就用不了。 如果想要去官网的续费页&…...
考研人考研魂——英语单词篇(20231010)
下一站,上岸 transplanttransportstorestoragestockstridestrikestringstructurestrikingstunprimaryprimeprimitiveprincipalpsychiatryprinciplepsychologyliableliberal transplant n. (器官等的)移植;移植的器官 vt. 移植&a…...
java 版 项目管理工程系统,实现项目全周期管理-源码交付
工程项目管理软件(工程项目管理系统)对建设工程项目管理组织建设、项目策划决策、规划设计、施工建设到竣工交付、总结评估、运维运营,全过程、全方位的对项目进行综合管理 工程项目各模块及其功能点清单 一、系统管理 1、数据字典&am…...
TOGAF(企业架构)
TOGAF 核心概念(官方原版) 什么是TOGAF? TOGAF?是一种经验证的企业架构方法和框架,被世界领先的组织用于提高业务效率。它是一个企业架构标准,确保企业架构专业人员之间的标准、方法和通信一致,以便我们…...
wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...
新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
