008-SpringBoot 限流

devtools/2025/1/8 8:07:05/

SpringBoot 限流

  • 一、引入依赖
  • 二、创建注解
  • 三、Redis 配置
  • 四、创建切面
            • 1.第一种写法:
            • 2.第二种写法:
  • 五、配置 Application
  • 六、工具
  • 七、测试 Controller
  • 八、演示结果

自定义注解助力系统保护与高效运行

一、引入依赖

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.0</version>
</parent>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

二、创建注解

java">@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RateLimiter {String key() default "rate_limit:";/*** 限流时间,单位秒* @return*/int time() default 5;/*** 限流次数* @return*/int count() default 10;
}

三、Redis 配置

java">@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(redisConnectionFactory);// 设置 key 和 value 的序列化方式,可以根据需要进行定制template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new GenericJackson2JsonRedisSerializer());return template;}@Beanpublic DefaultRedisScript<Long> limitScript() {DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();redisScript.setScriptText(limitScriptText());redisScript.setResultType(Long.class);return redisScript;}/*** 限流脚本*/private String limitScriptText() {return "local key = KEYS[1]\n" +"local count = tonumber(ARGV[1])\n" +"local time = tonumber(ARGV[2])\n" +"local current = redis.call('get', key);\n" +"if current and tonumber(current) > count then\n" +"    return tonumber(current);\n" +"end\n" +"current = redis.call('incr', key)\n" +"if tonumber(current) == 1 then\n" +"    redis.call('expire', key, time)\n" +"end\n" +"return tonumber(current);";}
}

四、创建切面

1.第一种写法:
java">@Slf4j
@Aspect // 标记为切面类
@Component
public class RateLimiterAspect {@Autowired // 注入RedisTemplate实例private RedisTemplate<String, Object> redisTemplate; // Redis操作模板@Autowired // 注入RedisScript实例private RedisScript<Long> limitScript; // 用于执行Lua脚本的RedisScript实例/*** 在方法执行之前进行限流检查。** @param point 当前JoinPoint(连接点)*/@Before("@annotation(org.example.common.annotation.RateLimiter)") // 在带有RateLimiter注解的方法执行前触发public void doBefore(JoinPoint point) {MethodSignature signature = (MethodSignature) point.getSignature(); // 获取方法签名Method method = signature.getMethod(); // 获取被通知的方法// 在这里,你可以获取方法上的注解RateLimiter annotation = method.getAnnotation(RateLimiter.class);if (annotation == null) {// 注解对象为空,直接返回return;}// 获取RateLimiter注解中的时间窗口长度int time = annotation.time();// 获取RateLimiter注解中的请求次数限制int count = annotation.count();// 组合限流键名String combineKey = getCombineKey(annotation,point);// 将组合后的键名封装成ListList<String> keys = Collections.singletonList(combineKey);try {// 使用RedisTemplate执行Lua脚本,传递键名、请求次数限制和时间窗口长度作为参数Long number = redisTemplate.execute(limitScript, keys, count, time);// 如果返回的数字大于请求次数限制,则抛出异常提示请求过于频繁if (number != null && number > count) {throw new RuntimeException("请求过于频繁,请稍后再试");}} catch (Exception ex) {// 打印异常堆栈信息ex.printStackTrace();}}/*** 获取组合的限流键名。** @param point 当前JoinPoint(连接点)* @return 组合后的限流键名*/public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest())).append("-");MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();Class<?> targetClass = method.getDeclaringClass();stringBuffer.append(targetClass.getName()).append("-").append(method.getName());return stringBuffer.toString();}}
2.第二种写法:
java">@Slf4j
@Aspect // 标记为切面类
@Component
public class RateLimiterAspect {@Autowired // 注入RedisTemplate实例private RedisTemplate<String, Object> redisTemplate; // Redis操作模板@Autowired // 注入RedisScript实例private RedisScript<Long> limitScript; // 用于执行Lua脚本的RedisScript实例/*** 在方法执行之前进行限流检查。** @param point 当前JoinPoint(连接点)*/@Before("@annotation(rateLimiter)")public void doBefore(JoinPoint point, RateLimiter rateLimiter) {// 获取RateLimiter注解中的时间窗口长度int time = rateLimiter.time();// 获取RateLimiter注解中的请求次数限制int count = rateLimiter.count();// 组合限流键名String combineKey = getCombineKey(rateLimiter,point);// 将组合后的键名封装成ListList<String> keys = Collections.singletonList(combineKey);try {// 使用RedisTemplate执行Lua脚本,传递键名、请求次数限制和时间窗口长度作为参数Long number = redisTemplate.execute(limitScript, keys, count, time);// 如果返回的数字大于请求次数限制,则抛出异常提示请求过于频繁if (number != null && number > count) {throw new RuntimeException("请求过于频繁,请稍后再试");}} catch (Exception ex) {// 打印异常堆栈信息ex.printStackTrace();}}/*** 获取组合的限流键名。** @param point 当前JoinPoint(连接点)* @return 组合后的限流键名*/public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {StringBuffer stringBuffer = new StringBuffer(rateLimiter.key());stringBuffer.append(IpUtils.getIpAddr(ServletUtils.getRequest())).append("-");MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();Class<?> targetClass = method.getDeclaringClass();stringBuffer.append(targetClass.getName()).append("-").append(method.getName());return stringBuffer.toString();}}

五、配置 Application

server:port: 8081spring:# redis 配置redis:# 地址host: 127.0.0.1# 端口,默认为6379port: 6379# 数据库索引database: 0# 密码password:# 连接超时时间timeout: 10slettuce:pool:# 连接池中的最小空闲连接min-idle: 0# 连接池中的最大空闲连接max-idle: 8# 连接池的最大数据库连接数max-active: 8# #连接池最大阻塞等待时间(使用负值表示没有限制)max-wait: -1ms

六、工具

java">public class IpUtils {public static String getIpAddr(HttpServletRequest request){if (request == null){return "unknown";}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("X-Forwarded-For");}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.getHeader("X-Real-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){ip = request.getRemoteAddr();}return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : clean(ip);}public static String clean(String content){return new HTMLFilter().filter(content);}}
java">public class ServletUtils {/*** 获取request*/public static HttpServletRequest getRequest(){return getRequestAttributes().getRequest();}public static ServletRequestAttributes getRequestAttributes(){RequestAttributes attributes = RequestContextHolder.getRequestAttributes();return (ServletRequestAttributes) attributes;}
}

七、测试 Controller

java">@RestController
public class TestController {/*** 测试接口方法,用于返回一个简单的字符串响应。** @RateLimiter 注解用于限制此方法的访问频率。* 该方法在10秒的时间窗口内最多只能被调用20次。** @return 返回一个字符串,表示这是测试接口*///限流注解,time为时间窗口长度(秒),count为时间窗口内的最大请求次数@RateLimiter(time = 60, count = 2)@GetMapping("/test")  // GET请求映射到/test路径public String test() {return "测试接口";  // 返回字符串表示这是测试接口}
}

八、演示结果

在这里插入图片描述


http://www.ppmy.cn/devtools/147316.html

相关文章

计算机网络 (14)数字传输系统

一、定义与原理 数字传输系统&#xff0c;顾名思义&#xff0c;是一种将连续变化的模拟信号转换为离散的数字信号&#xff0c;并通过适当的传输媒介进行传递的系统。在数字传输系统中&#xff0c;信息被编码成一系列的二进制数字&#xff0c;即0和1&#xff0c;这些数字序列能够…

C语言插入排序及其优化

插入排序算法详解 插入排序是一种简单直观的排序算法。它通过构建有序序列&#xff0c;将未排序部分的元素插入到已排序部分的正确位置&#xff0c;直到所有元素排序完成。下面是插入排序的关键点及其实现细节。 算法思想 从第二个元素&#xff08;下标为 1&#xff09;开始&…

Python 向量检索库Faiss使用

Faiss&#xff08;Facebook AI Similarity Search&#xff09;是一个由 Facebook AI Research 开发的库&#xff0c;它专门用于高效地搜索和聚类大量向量。Faiss 能够在几毫秒内搜索数亿个向量&#xff0c;这使得它非常适合于实现近似最近邻&#xff08;ANN&#xff09;搜索&am…

Kubernetes第二天

1.pod运行一个容器 1.创建目录 mkdir -p /manifests/pod 2.编写pod资源清单文件 vim 01-myweb.yaml 说明&#xff1a; apiVersion:指的是Api的版本 metadata&#xff1a;资源的元数据 spec:用户期望的资源的运行状态 status&#xff1a;资源实际的运行状态 由于拉取远…

4种更快更简单实现Python数据可视化的方法

数据可视化是数据科学或机器学习项目中十分重要的一环。通常&#xff0c;你需要在项目初期进行探索性的数据分析&#xff08;EDA&#xff09;&#xff0c;从而对数据有一定的了解&#xff0c;而且创建可视化确实可以使分析的任务更清晰、更容易理解&#xff0c;特别是对于大规模…

2024年6月英语六级CET6写作与翻译笔记

目录 1 写作 1.1 数字素养和数字技能的重要性 1.2 独立自主学习 1.3 社会实践和学术学习同等重要 2 翻译 2.1 婚礼习俗(wedding customs) 2.2 扇子(Fans) 2.3 竹子(bamboo) 1 写作 1.1 数字素养和数字技能的重要性 1.2 独立自主学习 1.3 社会实践和学术学习同等重要 2…

【数据仓库】hadoop3.3.6 安装配置

文章目录 概述下载解压安装伪分布式模式配置hdfs配置hadoop-env.shssh免密登录模式设置初始化HDFS启动hdfs配置yarn启动yarn 概述 该文档是基于hadoop3.2.2版本升级到hadoop3.3.6版本&#xff0c;所以有些配置&#xff0c;是可以不用做的&#xff0c;下面仅记录新增操作&#…

【持续集成与持续部署(CI/CD)工具 - Jenkins】详解

持续集成与持续部署&#xff08;CI/CD&#xff09;工具 - Jenkins 一、持续集成与持续部署概述 &#xff08;一&#xff09;概念 持续集成&#xff08;Continuous Integration&#xff0c;CI&#xff09;&#xff1a;是一种软件开发实践&#xff0c;要求开发团队成员频繁地将…