java通用自研接口限流组件

某业务中需要对后端接口进行限流,我们可以直接引入阿里巴巴的Sentinel快速实现,但是某企业中出于安全考虑,需要部门自己研发一套,可以采用Redis+Lua脚本+AOP+反射+自定义注解来实现
思路来源于链接

项目结构:
在这里插入图片描述
启动类:不需要额外加东西,默认即可
在这里插入图片描述
pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.example</groupId>
    <artifactId>my-rate-limit</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>my-rate-limit</name>
    <description>my-rate-limit</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>21</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.22.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.properties:配置redis相关

spring.application.name=my-rate-limit
server.port=8081
# redis
spring.data.redis.database=0
spring.data.redis.host=127.0.0.1
spring.data.redis.port=6379
spring.data.redis.password=
spring.data.redis.lettuce.pool.max-active=8
spring.data.redis.lettuce.pool.max-wait=-1ms
spring.data.redis.lettuce.pool.max-idle=8
spring.data.redis.lettuce.pool.min-idle=0

RedisConfig:常规的序列化配置

package org.example.myratelimit.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
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;

/**
 * RedisConfig
 * @author xiajunfeng
 * @date 2025/03/08
 */
@Configuration
@EnableAspectJAutoProxy //V2  开启AOP自动代理
public class RedisConfig
{
    /**
     * @param lettuceConnectionFactory
     * @return
     *
     * redis序列化的工具配置类,下面这个请一定开启配置
     * 127.0.0.1:6379> keys *
     * 1) "ord:102"  序列化过
     * 2) "\xac\xed\x00\x05t\x00\aord:102"   野生,没有序列化过
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory)
    {
        RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();

        redisTemplate.setConnectionFactory(lettuceConnectionFactory);
        //设置key序列化方式string
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置value的序列化方式json
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }
}

以上都为常规配置,配置完成后,开始写接口

RedisLimitController:接口中用到了自定义注解**@RedisLimitAnnotation**

package org.example.myratelimit.controller;

import lombok.extern.slf4j.Slf4j;
import org.example.myratelimit.annotation.RedisLimitAnnotation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * RedisLimitController
 * @author xiajunfeng
 * @date 2025/03/08
 */
@Slf4j
@RestController
public class RedisLimitController {

    /**
     * 10秒内最多访问3次
     * @return
     */
    @GetMapping("/redis/limit/test")
    @RedisLimitAnnotation(key = "redisLimit", permitsPerSecond = 3, expire = 10, msg = "当前访问人数较多,请稍后再试,自定义提示!")
    public String redisLimit()
    {
        return "正常业务返回,订单流水:xxxx" ;
    }

}

RedisLimitAnnotation:自定义注解,定义了key、最大访问次数、过期窗口时间、限流出现提示语

package org.example.myratelimit.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author xiajunfeng
 */

@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你点击太快,请稍后再试,谢谢";
}


RedisLimitAop:使用AOP技术实现关键限流逻辑,调用了lua脚本

package org.example.myratelimit.aop;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
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.example.myratelimit.annotation.RedisLimitAnnotation;
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.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * RedisLimitAop
 * @author xiajunfeng
 * @date 2025/03/08
 */
@Slf4j
@Aspect
@Component
public class RedisLimitAop {
    Object result = null;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     *  redis调用lua脚本
     */
    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(org.example.myratelimit.annotation.RedisLimitAnnotation)")
    public Object around(ProceedingJoinPoint joinPoint){
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        RedisLimitAnnotation redisLimitAnnotation = method.getAnnotation(RedisLimitAnnotation.class);
        // redis key
        String key = redisLimitAnnotation.key();
        // @RedisLimitAnnotation中的key不能为空
        if(key == null){
            throw new RuntimeException("请在注解属性中定义key");
        }
        // 最多的访问限制次数
        long limit = redisLimitAnnotation.permitsPerSecond();
        // 过期时间(计算窗口时间)
        long expire = redisLimitAnnotation.expire();
        // redis key 数组
        List<String> keys = new ArrayList<>();
        keys.add(key);

        // 执行lua脚本后的返回结果
        Long count = stringRedisTemplate.execute(
            redisLuaScript,
            keys,
            String.valueOf(limit),
            String.valueOf(expire)
        );
        // 如果结果为0,证明已经成功运行限流功能
        if (count == 0) {
            System.out.println("启动了限流功能,key: "+key);
            // 返回@RedisLimitAnnotation中自定义的msg内容
            return redisLimitAnnotation.msg();
        }

        try {
            //放行
            result = joinPoint.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }

        return result;
    }

}

rateLimiter.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
    -- 自增长 1
    redis.call('INCRBY', key, 1)
    -- 设置过期时间
    redis.call('EXPIRE', key, ARGV[2])
    return curentLimit + 1
end

--@RedisLimitAnnotation(key = "redisLimit", permitsPerSecond = 2, expire = 1, msg = "当前排队人数较多,请稍后再试!")

编写完成后,启动服务,访问http://localhost:8081/redis/limit/test,我的自定义注解配置为@RedisLimitAnnotation(key = “redisLimit”, permitsPerSecond = 3, expire = 10, msg = “当前访问人数较多,请稍后再试,自定义提示!”)
即10秒内最多访问3次,浏览器快速访问这个接口三次,浏览器出现错误提示,成功!
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/983394.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

苹果笔记本换电池攻略

苹果笔记本换电池攻略 笔记本型号 MacBook Pro A1708 难点或容易出问题的点 1、开后盖 开后盖是个技巧活&#xff0c;差点因为打不开后盖直接放弃自己换电池。主要是掌握不好力度&#xff0c;太用力怕掰坏了&#xff0c;不用力又打不开。 2、撕电池接口保护膜 有胶&#x…

【笔记】STM32L4系列使用RT-Thread Studio电源管理组件(PM框架)实现低功耗

硬件平台&#xff1a;STM32L431RCT6 RT-Thread版本&#xff1a;4.1.0 目录 一.新建工程 二.配置工程 ​编辑 三.移植pm驱动 四.配置cubeMX 五.修改驱动文件&#xff0c;干掉报错 六.增加用户低功耗逻辑 1.设置唤醒方式 2.设置睡眠时以及唤醒后动作 ​编辑 3.增加测试命…

【计网】应用层

应用层 6.1 应用层概述6.2 客户/服务器&#xff08;C/S&#xff09;方式和对等&#xff08;P2P&#xff09;方式6.3 动态主机配置协议DHCP6.4 域名系统DNS6.5 文件传送协议FTP6.6 电子邮件6.7 万维网WWW 6.1 应用层概述 6.2 客户/服务器&#xff08;C/S&#xff09;方式和对等&…

蓝桥杯省赛—dfs算法

一.题目 二.代码实现 public class Main {public static int max 0;//结果public static int x;//数组大小public static boolean[] b;//判断数组public static int[] array;//数组记录public static int start;public static void main(String[] args) {Scanner scan new S…

Docker 学习(四)——Dockerfile 创建镜像

Dockerfile是一个文本格式的配置文件&#xff0c;其内包含了一条条的指令(Instruction)&#xff0c;每一条指令构建一层&#xff0c;因此每一条指令的内容&#xff0c;就是描述该层应当如何构建。有了Dockerfile&#xff0c;当我们需要定制自己额外的需求时&#xff0c;只需在D…

Flink深入浅出之05:CEP复杂事件

深入浅出Flink-第五天 1️⃣深入理解Flink的CEP的机制和使用&#xff0c;Flink实时处理应用案例。 4️⃣ 要点 &#x1f4d6; 1. Flink的复杂事件处理机制CEP 1.1 CEP概念 CEP是Complex Event Processing三个单词的缩写&#xff0c;表示复杂事件处理&#xff0c;是一种基于…

指令的旋律:走进Linux系统开发工具,体悟Ubuntu系统下软件包管理器的奥秘

文章目录 引言一、软件包的基本概念二. APT2.1 更新软件包列表2.2 升级已安装的软件包2.3 安装软件包2.4 卸载软件包2.5 查找软件包2.6 显示软件包信息2.7 清理不需要的包 三、 APT的配置文件四、APT的源管理4.1 软件源4.2 添加PPA&#xff08;Personal Package Archive&#x…

队列相关练习

目录 1、用队列实现栈 2、用栈实现队列 1、用队列实现栈 oj&#xff1a;225. 用队列实现栈 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 1. 入栈时&#xff0c;两个队列 哪个不为空放到哪个队列里&#xff0c;两个都是空的指定放到第一个里面 2. 出栈时&#…

全新方案80M/S,告别限速!

资源和文件转载的最佳方式是通过网盘链接分享&#xff0c;这种方式的优点在于可以避免地区、局域网和文件大小的限制。近年来&#xff0c;随着123云盘、阿里云盘和夸克网盘等网盘逐步崭露头角&#xff0c;各具优势。然而&#xff0c;依然没能撼动百度网盘老大哥的位置&#xff…

海思Hi3516DV00移植yolov5-7.0的模型转化流程说明

一、YOLOv5 YOLOv5作为单阶段检测框架的集大成者&#xff0c;凭借其卓越的实时性、高精度和易用性&#xff0c;已成为工业界实际部署的首选方案。yolov5的最新版本是7.0&#xff0c;该版本是官方最后更新的一个版本。yolov5-7.0 工程化实现卓越&#xff1a;基于PyTorch框架构…

通过Golang的container/list实现LRU缓存算法

文章目录 力扣&#xff1a;146. LRU 缓存主要结构 List 和 Element常用方法1. 初始化链表2. 插入元素3. 删除元素4. 遍历链表5. 获取链表长度使用场景注意事项 源代码阅读 在 Go 语言中&#xff0c;container/list 包提供了一个双向链表的实现。链表是一种常见的数据结构&#…

从零开始学机器学习——线性和多项式回归

首先给大家介绍一个很好用的学习地址&#xff1a;https://cloudstudio.net/columns 在之前的学习中&#xff0c;我们已经对数据的准备工作以及数据可视化有了一定的了解。今天&#xff0c;我们将深入探讨基本线性回归和多项式回归的概念与应用。 如果在过程中涉及到一些数学知…

【数据结构初阶第十八节】八大排序系列(上篇)—[详细动态图解+代码解析]

看似不起眼的日复一日&#xff0c;总会在某一天让你看到坚持的意义。​​​​​​云边有个稻草人-CSDN博客 hello&#xff0c;好久不见&#xff01; 目录 一. 排序的概念及运用 1. 概念 2. 运用 3. 常见排序算法 二. 实现常见排序算法 1. 插入排序 &#xff08;1&…

SPI驱动五) -- SPI_DAC上机实验(使用spidev)

文章目录 参考资料&#xff1a;一、DAC硬件1.1 原理图1.2 扩展板连接图1.3 DAC原理 二、编写APP三、编写设备树四、上机实验五、Bug分析六、总结 参考资料&#xff1a; 参考资料&#xff1a; 内核驱动&#xff1a;drivers\spi\spidev.c 内核提供的测试程序&#xff1a;tools\…

基于Asp.net的驾校管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

Spring (八)AOP-切面编程的使用

目录 实现步骤&#xff1a; 1 导入AOP依赖 2 编写切面Aspect 3 编写通知方法 4 指定切入点表达式 5 测试AOP动态织入 图示&#xff1a; 实现步骤&#xff1a; 1 导入AOP依赖 <!-- Spring Boot AOP依赖 --><dependency><groupId>org.springframework.b…

阿里云CTF2025 ---Web

ezoj 啊&#xff1f;怎么整个五个算法题给CTF选手做&#xff1f;&#xff1f;这我不得不展示一下真正的技术把测评机打穿。 可以看到源码 import os import subprocess import uuid import json from flask import Flask, request, jsonify, send_file from pathlib import Pa…

WSL(ubunt)中使用ollama部署deepseek-7b

想在自己的Win11电脑上部署Linux的DeepSeek模型&#xff0c;但在网上一直没有找到合适的相应教程&#xff0c;自己查询各种网上资源&#xff0c;以及询问一些AI大模型后成功安装&#xff0c;并整理了以下步骤。仅作为个人学习笔记使用&#xff0c;由于本人对各方面知识掌握不足…

蓝桥杯国赛—路径之谜(dfs详细解法)

一.题目 二.dfs解法 使用dfs算法可以递归遍历所有可能路径&#xff0c;如果找到错误的路径就进行回溯&#xff0c;只有找到正确的路径才会输出 public class Main {static class pair{int x;int y;public pair(int x,int y){this.x x;this.y y;} }public static void bfs()…

Jenkins在Windows上的使用(二):自动拉取、打包、部署

&#xff08;一&#xff09;Jenkins全局配置 访问部署好的Jenkins服务器网址localhost:8080&#xff0c;完成默认插件的安装后&#xff0c;接下来将使用SSH登录远程主机以实现自动化部署。 1. 配置插件 选择dashboard->Manage Jenkins->plugins 安装下面两个插件  …