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

news2024/11/25 2:29:57

定义注解

@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;

    @Resource
    private StringRedisTemplate stringRedisTemplate;


    private DefaultRedisScript<Long> redisLuaScript;
    @PostConstruct
    public 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的key
            String 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脚本

rateLimiter.lua放在resource目录下

--获取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
    -- 自增长 1
    redis.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.coloradmin.cn/o/1941867.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

netty使用redis发布订阅实现消息推送

netty使用redis发布订阅实现消息推送 场景 项目中需要给用户推送消息: 接口 RestController public class PushApi {Autowiredprivate PushService pushService;/*** 消息推送* param query* return*/PostMapping("/push/message")public String push(RequestBody…

华为Ascend C算子开发(中级)考试

华为Ascend C算子开发&#xff08;中级&#xff09;考试题 提示&#xff1a;这个是河北廊坊Ascend C算子开发考试题和答案&#xff0c;仅供参考&#xff0c;因为不确定其他城市的考试题是否也是一样 文章目录 华为Ascend C算子开发&#xff08;中级&#xff09;考试题一、op_ho…

音视频入门基础:H.264专题(16)——FFmpeg源码中,判断某文件是否为H.264裸流文件的实现

一、引言 通过FFmpeg命令可以判断出某个文件是否为AnnexB格式的H.264裸流&#xff1a; 所以FFmpeg是怎样判断出某个文件是否为AnnexB格式的H.264裸流呢&#xff1f;它内部其实是通过h264_probe函数来判断的。从文章《FFmpeg源码&#xff1a;av_probe_input_format3函数分析》中…

VSCode STM32嵌入式开发插件记录

要卸载之前搭建的VSCode嵌入式开发环境了&#xff0c;记录一下用的插件。 1.Cortex-Debug https://github.com/Marus/cortex-debug 2.Embedded IDE https://github.com/github0null/eide 3.Keil uVision Assistant https://github.com/jacksonjim/keil-assistant/ 4.RTO…

python-爬虫实例(4):获取b站的章若楠的视频

目录 前言 道路千万条&#xff0c;安全第一条 爬虫不谨慎&#xff0c;亲人两行泪 获取b站的章若楠的视频 一、话不多说&#xff0c;先上代码 二、爬虫四步走 1.UA伪装 2.获取url 3.发送请求 4.获取响应数据进行解析并保存 总结 前言 道路千万条&#xff0c;安全第一条 爬…

剧本杀小程序搭建,互联网下的游戏新体验,实现新增收!

近几年&#xff0c;桌游备受大众青睐&#xff0c;剧本杀行业更是瞬间曝火&#xff01;拥有强大社交体验与沉浸式游戏体验的剧本杀成为了众多年轻人的新宠&#xff0c;无论是外出游玩还是好友聚会&#xff0c;剧本杀游戏都成为了首选方式。 随着互联网的发展&#xff0c;线上小…

【防火墙】防火墙NAT、智能选路综合实验

实验拓扑 实验要求 7&#xff0c;办公区设备可以通过电信链路和移动链路上网(多对多的NAT&#xff0c;并且需要保留一个公网IP不能用来转换) 8&#xff0c;分公司设备可以通过总公司的移动链路和电信链路访问到Dmz区的http服务器 9&#xff0c;多出口环境基于带宽比例进行选路…

IO多路复用-select的使用详解【C语言】

1.多进程/线程并发和IO多路复用的对比 IO多路转接也称为IO多路复用&#xff0c;它是一种网络通信的手段&#xff08;机制&#xff09;&#xff0c;通过这种方式可以同时监测多个文件描述符并且这个过程是阻塞的&#xff0c;一旦检测到有文件描述符就绪&#xff08; 可以读数据…

【Java】中的List集合

目录 一、什么是List集合二、List的常用方法List的初始化元素操作1.添加元素2.删除元素3.修改元素4.查询元素 三、List集合的遍历1.for循环遍历2.增强for循环3.迭代器遍历 一、什么是List集合 List集合是最常用的一种数据结构之一。它具有动态扩容、元素添加、删除和查询等基础…

MySQL--索引(2)

InnoDB 1.索引类型 主键索引(Primary Key) 数据表的主键列使用的就是主键索引。 一张数据表有只能有一个主键&#xff0c;并且主键不能为 null&#xff0c;不能重复。 在 mysql 的 InnoDB 的表中&#xff0c;当没有显示的指定表的主键时&#xff0c;InnoDB 会自动先检查表中是…

IC秋招RTL代码合集

一 全加器和半加器 全加器 module full_adder1(input Ai, Bi, Ci,output So, Co);assign So Ai ^ Bi ^ Ci ;assign Co (Ai & Bi) | (Ci & (Ai | Bi)); endmodule module full_adder1(input Ai, Bi, Cioutput So, Co);assign {Co, So} Ai Bi Ci ; endm…

Wi-SUN无线通信技术 — 大规模分散式物联网应用首选

引言 在数字化浪潮的推动下&#xff0c;物联网&#xff08;IoT&#xff09;正逐渐渗透到我们生活的方方面面。Wi-SUN技术以其卓越的性能和广泛的应用前景&#xff0c;成为了大规模分散式物联网应用的首选。本文将深入探讨Wi-SUN技术的市场现状、核心优势、实际应用中的案例以及…

AndroidStudio 编辑xml布局文件卡死问题解决

之前项目编写的都是正常&#xff0c;升级AndroidStudio后编辑布局文件就卡死&#xff0c;还以为是AndroidStudio文件。 其实不然&#xff0c;我给整个项目增加了版权声明。所以全部跟新后&#xff0c;布局文件也增加了版权声明。估计AndroidStudio在 解析布局文件时候因为有版…

信号【Linux】

文章目录 信号处理方式&#xff08;信号递达&#xff09;前后台进程 终端按键产生信号kill系统调用接口向进程发信号阻塞信号sigset_tsigprocmasksigpending内核态与用户态&#xff1a;内核空间与用户空间内核如何实现信号的捕捉 1、信号就算没有产生&#xff0c;进程也必须识别…

Django—admin后台管理

Django官网 https://www.djangoproject.com/ 如果已经有了Django跳过这步 安装Django&#xff1a; 如果你还没有安装Django&#xff0c;可以通过Python的包管理器pip来安装&#xff1a; pip install django 创建项目&#xff1a; 使用Django创建一个新的项目&#xff1a; …

敲详细的springboot中使用RabbitMQ的源码解析

这里介绍的源码主要是涉及springboot框架下的rabbitmq客户端代码&#xff08;具体在springframework.amqp.rabbit包下&#xff0c;区分一下不由springboot直接接管的spring-rabbit的内容&#xff09;&#xff0c;springboot基于RabbitMQ的Java客户端建立了简便易用的框架。 sp…

jmeter实战(1)- Mac环境安装

一、安装 JDK 这个就不介绍了&#xff0c;本地自行安装 JDK 并且配置好环境变量 二、安装 Jmeter 1. 下载地址 —> 下载链接点击这里 2. 选择合适的版本下载 3. 解压到本地目录 解压后&#xff0c;会得到下面的目录文件&#xff1a; 输入cd bin&#xff0c;进入到bin…

OpenCV 直方图概念,直方图均衡化原理详解

文章目录 直方图相关概念颜色灰度级作用应用场景 C 使用OpenCV绘制直方图单通道直方图关键代码分析&#xff1a;calcHist函数分析使用OpenCV API来绘制直方图 效果图&#xff1a; 彩色三通道直方图效果图&#xff1a; 直方图均衡化概念均衡化作用均衡化效果均衡化数学原理步骤数…

项目实战二 HIS项目

目标&#xff1a; 项目的操作流程&#xff1a; 开发体系 前端开发&#xff1a;负责页面的编写 HTML CSS JavaScript 后端开发&#xff1a;看不到 摸不着的功能 常用开发语言 PHP JAVA Python 框架 &#xff1a; 半成品 做好的功能模块 版本控制 Git 分布式版本控…

vxe-table——实现切换页码时排序状态的回显问题(ant-design+elementUi中table排序不同时回显的bug)——js技能提升

之前写的后台管理系统&#xff0c;都是用的antdelement&#xff0c;table组件中的【排序】问题是有一定的缺陷的。 想要实现的效果&#xff1a; antv——table组件一次只支持一个参数的排序 如下图&#xff1a; 就算是可以自行将排序字段拼接到列表接口的入参中&#xff0c…