Redis+Lua脚本+AOP+反射+自定义注解,打造我司内部基础架构限流组件

news/2024/9/25 17:14:03/

定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface RedisLimitAnnotation
{/*** 资源的key,唯一* 作用:不同的接口,不同的流量控制*/String key() default "";/*** 最多的访问限制次数*/long permitsPerSecond() default 3;/*** 过期时间(计算窗口时间),单位秒默认30*/long expire() default 30;/*** 默认温馨提示语*/String msg() default "default message:系统繁忙or你点击太快,请稍后再试,谢谢";
}

定义AOP

import com.atguigu.interview2.annotations.RedisLimitAnnotation;
import com.atguigu.interview2.exception.RedisLimitException;
import lombok.extern.slf4j.Slf4j;
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.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 org.springframework.core.io.ClassPathResource;import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;@Slf4j
@Aspect
@Component
public class RedisLimitAop
{Object result = null;@Resourceprivate StringRedisTemplate stringRedisTemplate;private DefaultRedisScript<Long> redisLuaScript;@PostConstructpublic void init(){redisLuaScript = new DefaultRedisScript<>();redisLuaScript.setResultType(Long.class);redisLuaScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("rateLimiter.lua")));}@Around("@annotation(com.atguigu.interview2.annotations.RedisLimitAnnotation)")public Object around(ProceedingJoinPoint joinPoint){System.out.println("---------环绕通知1111111");MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();//拿到RedisLimitAnnotation注解,如果存在则说明需要限流,容器捞鱼思想RedisLimitAnnotation redisLimitAnnotation = method.getAnnotation(RedisLimitAnnotation.class);if (redisLimitAnnotation != null){//获取redis的keyString key = redisLimitAnnotation.key();String className = method.getDeclaringClass().getName();String methodName = method.getName();String limitKey = key +"\t"+ className+"\t" + methodName;log.info(limitKey);if (null == key){throw new RedisLimitException("it's danger,limitKey cannot be null");}long limit = redisLimitAnnotation.permitsPerSecond();long expire = redisLimitAnnotation.expire();List<String> keys = new ArrayList<>();keys.add(key);Long count = stringRedisTemplate.execute(redisLuaScript,keys,String.valueOf(limit),String.valueOf(expire));System.out.println("Access try count is "+count+" \t key= "+key);if (count != null && count == 0){System.out.println("启动限流功能key: "+key);return redisLimitAnnotation.msg();}}try {result = joinPoint.proceed();//放行} catch (Throwable e) {throw new RuntimeException(e);}System.out.println("---------环绕通知2222222");System.out.println();System.out.println();return result;}}

lua_134">lua脚本

rateLimiter.lua放在resource目录下

lua">--获取KEY,针对那个接口进行限流,Lua脚本中的数组索引默认是从1开始的而不是从零开始。
local key = KEYS[1]
--获取注解上标注的限流次数
local limit = tonumber(ARGV[1])local curentLimit = tonumber(redis.call('get', key) or "0")--超过限流次数直接返回零,否则再走else分支
if curentLimit + 1 > limit
then return 0
-- 首次直接进入
else-- 自增长 1redis.call('INCRBY', key, 1)-- 设置过期时间redis.call('EXPIRE', key, ARGV[2])return curentLimit + 1
end

接口使用

@Slf4j
@RestController
public class RedisLimitController
{/*** Redis+Lua脚本+AOP+反射+自定义注解,打造我司内部基础架构限流组件* 在redis中,假定一秒钟只能有3次访问,超过3次报错* key = redisLimit* Value = permitsPerSecond设置的具体值* 过期时间 = expire设置的具体值,* permitsPerSecond = 3, expire = 10* 表示本次10秒内最多支持3次访问,到了3次后开启限流,过完本次10秒钟后才解封放开,可以重新访问*/@GetMapping("/redis/limit/test")@RedisLimitAnnotation(key = "redisLimit", permitsPerSecond = 3, expire = 10, msg = "当前访问人数较多,请稍后再试,自定义提示!")public String redisLimit(){return "正常业务返回,订单流水:"+ IdUtil.fastUUID();}
}

测试效果

连续刷新几次调接口,第四次就会限流
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
对于公司不能用第三方组件来限流的时候,这个方法很哇塞,下课!


http://www.ppmy.cn/news/1499829.html

相关文章

计算机网络基础:1.上网设备与流程、OSI七层模型、TCP/IP五层模型

你正在经营一家繁忙的餐厅&#xff0c;顾客们点餐并期待着美味的食物。我们可以将网络的各个层次和设备比作餐厅的不同部分。 一、上网设备 网卡&#xff1a;就像是餐厅的点餐系统&#xff0c;顾客通过它来下单&#xff0c;而厨房通过它来接收订单。上网设备必须有网卡&#x…

基于 HTML+ECharts 实现智慧工地数据可视化大屏(含源码)

构建智慧工地数据可视化大屏&#xff1a;基于 HTML 和 ECharts 的实现 智慧工地已成为建筑行业的新趋势。通过实时监控和数据分析&#xff0c;智慧工地可以提高施工效率、降低安全风险。本文将详细介绍如何利用 HTML 和 ECharts 实现一个功能强大的智慧工地数据可视化大屏。 源…

学习记录--Bert、Albert、RoBerta

目录 Bert 1&#xff1a;输入 2&#xff1a;Bert结构 3&#xff1a;模型预训练 Albert 1&#xff1a;SOP任务 2&#xff1a;embedding因式分解 3&#xff1a;参数共享 RoBerta 参考&#xff1a; BERT原理和结构详解_bert结构-CSDN博客 [LLM] 自然语言处理 --- ALBER…

跨境电商独立站:Shopify/Wordpress/店匠选哪个?

在面对不断增加的平台运营压力时&#xff0c;不少跨境电商的商家逐渐将注意力转向建立自己的独立站。据《中国跨境出口电商发展报告&#xff08;2022&#xff09;》所示&#xff0c;中国拥有的独立站数量在2022年已接近20万个&#xff0c;这表明独立站已成为卖家拓展海外市场的…

【QT】QT 窗口(菜单栏、工具栏、状态栏、浮动窗口、对话框)

Qt 窗口是通过 QMainWindow类来实现的。 QMainWindow 是一个为用户提供主窗口程序的类&#xff0c;继承自 QWidget 类&#xff0c;并且提供了⼀个预定义的布局。QMainWindow 包含一个菜单栏&#xff08;Menu Bar&#xff09;、多个工具栏&#xff08;Tool Bars&#xff09;、…

软考:软件设计师 — 7.软件工程

七. 软件工程 1. 软件工程概述 &#xff08;1&#xff09;软件生存周期 &#xff08;2&#xff09;软件过程 软件开发中所遵循的路线图称为 "软件过程"。 针对管理软件开发的整个过程&#xff0c;提出了两个模型&#xff1a;能力成熟度模型&#xff08;CMM&#…

适用于 Mac 或 MacBook 的最佳数据恢复软件

Apple 设计的电脑可靠且用户友好&#xff0c;但即使是最好的最新款 MacBook硬件也会出现故障。当您的存储出现问题时&#xff0c;数据恢复软件可以帮助您恢复丢失和损坏的文件。 数据丢失的另一个原因是有时会发生令人尴尬的错误。如果您不小心丢弃了所需的文件&#xff0c;然…

gradle 构建项目添加版本信息

gradle 构建项目添加版本信息&#xff0c;打包使用 spring boot 的打包插件 build.gradle 配置文件 bootJar {manifest {attributes(Project-Name: project.name,Project-Version: project.version,"project-Vendor": "XXX Corp","Built-By": &…