Spring Boot 中使用 Redis + Aop 进行限流

Spring Boot 中使用 Redis 进行限流,通常你可以采用如下几种方式:

  1. 令牌桶算法(Token Bucket)
  2. 漏桶算法(Leaky Bucket)
  3. 固定窗口计数器(Fixed Window Counter)
  4. 滑动日志窗口(Sliding Log Window)

实现 Redis 限流,可以采用 Redis 提供的数据结构和功能脚本,如 Lua 脚本、Redisson 库等。以下是使用 Redis 和 Lua 脚本来实现令牌桶限流算法的示例:

步骤一:编写 Lua 脚本。

下面是一个限流的 Lua 脚本示例,实现基本的限流功能,放在Spring Boot项目下的resources目录下。

--获取KEY
local key = KEYS[1] -- 限流的 keylocal limit = tonumber(ARGV[1]) --注解标注的限流次数local curentLimit = tonumber(redis.call('get', key) or "0")if curentLimit + 1 > limit
then return 0
else-- 自增长 1redis.call('INCRBY', key, 1)-- 设置过期时间redis.call('EXPIRE', key, ARGV[2])return curentLimit + 1
end

步骤二:定义限流注解

package your.package;import java.lang.annotation.*;@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface RedisLimit {/*** 资源的key,唯一* 作用:不同的接口,不同的流量控制*/String key() default "";/*** 最多的访问限制次数*/long permitsPerSecond() default 2;/*** 过期时间也可以理解为单位时间,单位秒,默认60*/long expire() default 60;/*** 得不到令牌的提示语*/String msg() default "系统繁忙,请稍后再试.";
}

步骤三:定义Aop切面类

package your.package;import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
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 javax.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;/*** Limit AOP*/
@Slf4j
@Aspect
@Component
public class RedisLimitAop {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Pointcut("@annotation(your.package.RedisLimit)")private void check() {}private DefaultRedisScript<Long> redisScript;@PostConstructpublic void init() {redisScript = new DefaultRedisScript<>();redisScript.setResultType(Long.class);redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("rateLimiter.lua")));}@Before("check()")public void before(JoinPoint joinPoint) {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();//拿到RedisLimit注解,如果存在则说明需要限流RedisLimit redisLimit = method.getAnnotation(RedisLimit.class);if (redisLimit != null) {//获取redis的keyString key = redisLimit.key();String className = method.getDeclaringClass().getName();String name = method.getName();String limitKey = key + className + method.getName();log.info(limitKey);if (StringUtils.isEmpty(key)) {throw new RedisLimitException("key cannot be null");}long limit = redisLimit.permitsPerSecond();long expire = redisLimit.expire();List<String> keys = new ArrayList<>();keys.add(key);Long count = stringRedisTemplate.execute(redisScript, keys, String.valueOf(limit), String.valueOf(expire));log.info("Access try count is {} for key={}", count, key);if (count != null && count == 0) {log.debug("获取key失败,key为{}", key);throw new RedisLimitException(redisLimit.msg());}}}}

步骤四:自定义Redis限流异常

package your.package;/*** Redis限流自定义异常* @date 2023/3/10 21:43*/
public class RedisLimitException extends RuntimeException{public RedisLimitException(String msg) {super( msg );}
}

步骤五:自定义ResultInfo返回实体

package your.package;import lombok.Getter;
import lombok.Setter;@Getter
@Setter
public class ResultInfo<T> {private String message;private String code;private T data;public ResultInfo(String message, String code, T data) {this.message = message;this.code = code;this.data = data;}public static ResultInfo error(String message) {return new ResultInfo(message,"502",null);}}

步骤六:定义Controller接口

package your.package;import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@Slf4j
@RestController
@RequestMapping("/limit/redis")
public class LimitRedisController {/*** 基于Redis AOP限流*/@GetMapping("/test")@RedisLimit(key = "redis-limit:test", permitsPerSecond = 2, expire = 1, msg = "当前排队人数较多,请稍后再试!")public String test() {log.info("限流成功。。。");return "ok";}}

效果测试

在这里插入图片描述

实现了上面的步骤之后,Spring Boot应用就可以通过AOP与Redis来进行API限流了。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

“ReferenceError: AMap is not defined“

问题 笔者进行web开发&#xff0c;引入高德地图&#xff0c;控制台报错 "ReferenceError: AMap is not defined"详细问题 vue.runtime.esm.js:4662 [Vue warn]: Error in mounted hook: "ReferenceError: AMap is not defined"found in---> <Map&…

使用Windows API实现一个简单的串口助手

使用Windows API实现一个简单的串口助手 目录 使用window API开发一个具有字符串收发功能的串口助手 开发环境串口设备相关的API步骤实现代码收发测试图 使用window API开发一个具有字符串收发功能的串口助手 开发环境 Visual Studio 2015 串口设备相关的API CreateFile 参…

【LGR-177-Div.3】洛谷基础赛 #8 Westlake OI #1(仅A)

String Minimization a尽可能小的话&#xff0c;如果能和c交换更小就交换。 如果a和c相等&#xff0c;b交换能更小&#xff0c;就交换b。 #include <bits/stdc.h> //#define int long long #define per(i,j,k) for(int (i)(j);(i)<(k);(i)) #define rep(i,j,k) for(…

数据库系统原理实验报告1 | E-R图设计

整理自博主2021级专业课《数据库系统原理》自己完成的实验报告。 目录 一、实验目的 二、实验内容 1、某个学校有若干个系 2、某工厂生产若干产品 3、某学校的田径运动会中设置了各类比赛 4、自己调查一个需要提供开发数据库应用系统的单位 三、实验结果总结 四、实验结…

爬虫练习:获取某招聘网站Python岗位信息

一、相关网站 二、相关代码 import requests from lxml import etree import csv with open(拉钩Python岗位数据.csv, w, newline, encodingutf-8) as csvfile:fieldnames [公司, 规模,岗位,地区,薪资,经验要求]writer csv.DictWriter(csvfile, fieldnamesfieldnames)writer…

海外媒体宣发套餐如何利用3种方式洞察市场-华媒舍

在当今数字化时代&#xff0c;媒体宣发成为了企业推广产品和品牌的重要手段之一。其中&#xff0c;7FT媒体宣发套餐是一种常用而有效的宣传方式。本文将介绍这种媒体宣发套餐&#xff0c;以及如何利用它来洞察市场。 一、关键概念 在深入讨论7FT媒体宣发套餐之前&#xff0c;让…

day16_购物车(添加购物车,购物车列表查询,删除购物车商品,更新选中商品状态,完成购物车商品的全选,清空购物车)

文章目录 购物车模块1 需求说明2 环境搭建3 添加购物车3.1 需求说明3.2 远程调用接口开发3.2.1 ProductController3.2.2 ProductService 3.3 openFeign接口定义3.3.1 环境搭建3.3.2 接口定义3.3.3 降级类定义 3.4 业务后端接口开发3.4.1 添加依赖3.4.2 修改启动类3.4.3 CartInf…

vmware添加新磁盘

文章目录 前言一、新增磁盘二、初始化磁盘1.查看2.初始化3.挂载 总结 前言 虚拟机磁盘空间很散乱&#xff0c;大部分都在/root和/home下不好操作&#xff0c;故考虑新增磁盘、增加挂载点。 一、新增磁盘 右键打开虚拟机设置 二、初始化磁盘 1.查看 fdisk -l2.初始化 …

分布式执行引擎ray入门--(3)Ray Train

Ray Train中包含4个部分 Training function: 包含训练模型逻辑的函数 Worker: 用来跑训练的 Scaling configuration: 配置 Trainer: 协调以上三个部分 Ray TrainPyTorch 这一块比较建议直接去官网看diff&#xff0c;官网色块标注的比较清晰&#xff0c;非常直观。 impor…

CentOS 8启动流程

一、BIOS与UEFI BIOS Basic Input Output System的缩写&#xff0c;翻译过来就是“基本输入输出系统”&#xff0c;是一种业界标准的固件接口&#xff0c;第一次出现在1975年&#xff0c;是计算机启动时加载的第一个程序&#xff0c;主要功能是检测和设置计算机硬件&#xff…

验证码安全

目录 验证码识别&复用&调用&找回密码重定向&状态值 res 修改-找回密码修改返回状态值判定验证通过 验证码爆破-知道验证码规矩进行无次数限制爆破 短信轰炸原理 验证码识别&复用&调用&找回密码重定向&状态值 res 修改-找回密码修改返回状态…

【UE5】创建蓝图

创建GamePlay需要的相关蓝图 项目资源文末百度网盘自取 在 内容游览器 文件夹中创建文件夹&#xff0c;命名为 Blueprints &#xff0c;用来放这个项目的所有蓝图(Blueprint) 在 Blueprints 文件夹下新建文件夹 GamePlay ,用存放GamePlay相关蓝图 在 Blueprints 文件夹下创建文…