当前位置: 首页 > news >正文

基于Redis限流(aop切面+redis实现“令牌桶算法”)

令牌桶算法属于流量控制算法,在一定时间内保证一个键(key)的访问量不超过某个阈值。这里的关键是设置一个令牌桶,在某个时间段内生成一定数量的令牌,然后每次访问时从桶中获取令牌,如果桶中没有令牌,就拒绝访问。
参考网上一个博主写的:https://blog.csdn.net/xdx_dili/article/details/133683315
注意:我这边只是学习实践加上修改对应的代码记录下而已

第一步:记得要下载redis并配置好

第二步:创建springboot项目并引入maven,配置好配置文件
(注意我这边使用的springboot版本是2.6.x,因为2.7开始博主的部分代码不可用了)

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>21.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.78</version></dependency>
</dependencies>
#application.properties
#redis
spring.redis.host=127.0.0.1
spring.redis.port=6379server.port=8081

第三步:代码部分

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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;import java.io.Serializable;/*** RedisTemplate调用实例*/
@Configuration
public class RedisLimiterHelper {//spring会帮助我们注入LettuceConnectionFactory@Beanpublic RedisTemplate<String, Serializable> limitRedisTemplate(LettuceConnectionFactory redisConnectionFactory) {RedisTemplate<String, Serializable> template = new RedisTemplate<>();template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new GenericJackson2JsonRedisSerializer());template.setConnectionFactory(redisConnectionFactory);return template;}
}
/*** 限流类型枚举类*/
public enum LimitType {/*** 自定义key*/CUSTOMER,/*** 请求者IP*/IP;
}
import java.lang.annotation.*;/*** 自定义限流注解*/
//@Target({ElementType.METHOD, ElementType.TYPE}) 表示这个注解可以应用到方法和类上。
//ElementType.METHOD 表示这个注解可以应用到方法上,即可以在方法上添加这个注解。
//ElementType.TYPE 表示这个注解可以应用到类上,即可以在类上添加这个注解。
//@Retention(RetentionPolicy.RUNTIME) 表示这个注解的元数据信息(metadata)在运行时可用。
//@Inherited 表示这个注解可以被继承。
//@Documented 表示这个注解的文档信息(documentation)会被自动生成
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface RedisLimit {/*** 名字*/String name() default "";/*** key*/String key() default "";/*** Key的前缀*/String prefix() default "";/*** 给定的时间范围 单位(秒)*/int period();/*** 一定时间内最多访问次数*/int count();/*** 限流的类型(用户自定义key 或者 请求ip)*/LimitType limitType() default LimitType.CUSTOMER;}
import org.apache.commons.lang3.StringUtils;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;/*** 限流切面实现*/
@Aspect
@Configuration
public class LimitInterceptor {private static final Logger logger = LoggerFactory.getLogger(LimitInterceptor.class);private static final String UNKNOWN = "unknown";private final RedisTemplate<String, Serializable> limitRedisTemplate;@Autowiredpublic LimitInterceptor(RedisTemplate<String, Serializable> limitRedisTemplate) {this.limitRedisTemplate = limitRedisTemplate;}//切面(使用了该RedisLimit注解时触发)@Around("execution(public * *(..)) && @annotation(com.zhangximing.redis_springboot.annotate.RedisLimit)")public Object interceptor(ProceedingJoinPoint pjp) {MethodSignature signature = (MethodSignature) pjp.getSignature();Method method = signature.getMethod();RedisLimit limitAnnotation = method.getAnnotation(RedisLimit.class);LimitType limitType = limitAnnotation.limitType();String key;int limitPeriod = limitAnnotation.period();int limitCount = limitAnnotation.count();/*** 根据限流类型获取不同的key ,如果不传我们会以方法名作为key*/switch (limitType) {case IP:key = getIpAddress();break;case CUSTOMER:key = limitAnnotation.key();break;default:key = StringUtils.upperCase(method.getName());}List<String> keys = Arrays.asList(StringUtils.join(limitAnnotation.prefix(), key));try {//lua脚本String luaScript = buildLuaScript();//获取已调用数量RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);Long count = limitRedisTemplate.execute(redisScript, keys, limitCount, limitPeriod);//判断是否已超过限制if (count != null && count <= limitCount) {return pjp.proceed();} else {throw new RuntimeException("服务忙,请稍后再试");}} catch (Throwable e) {if (e instanceof RuntimeException) {
//                throw new RuntimeException(e.getLocalizedMessage());return e.getLocalizedMessage();}
//            throw new RuntimeException("server exception");return "服务异常";}}//编写 redis Lua 限流脚本public String buildLuaScript() {StringBuilder lua = new StringBuilder();lua.append("local c");//KEYS[1]表示keylua.append("\nc = redis.call('get',KEYS[1])");// 调用不超过最大值,则直接返回 (ARGV[1]表示第一个参数)lua.append("\nif c and tonumber(c) > tonumber(ARGV[1]) then");lua.append("\nreturn c;");lua.append("\nend");// 执行计算器自加lua.append("\nc = redis.call('incr',KEYS[1])");lua.append("\nif tonumber(c) == 1 then");// 从第一次调用开始限流,设置对应键值的过期lua.append("\nredis.call('expire',KEYS[1],ARGV[2])");lua.append("\nend");lua.append("\nreturn c;");return lua.toString();}//获取id地址public String getIpAddress() {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();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("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return ip;}
}
import com.zhangximing.redis_springboot.annotate.LimitType;
import com.zhangximing.redis_springboot.annotate.RedisLimit;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.atomic.AtomicInteger;/*** @Author: zhangximing* @Email: 530659058@qq.com* @Date: 2023/12/20 10:59* @Description: 限流测试类 参考 https://blog.csdn.net/xdx_dili/article/details/133683315*/
@RestController
@RequestMapping("/limit")
public class LimitController {private static final AtomicInteger ATOMIC_INTEGER_1 = new AtomicInteger();//十秒同一IP限制访问5次@RedisLimit(period = 10, count = 5, name = "测试接口", limitType = LimitType.IP)@RequestMapping("/test")public String testLimit(){return "SUCCESS:"+ATOMIC_INTEGER_1.incrementAndGet();}}

效果展示:
在这里插入图片描述

相关文章:

基于Redis限流(aop切面+redis实现“令牌桶算法”)

令牌桶算法属于流量控制算法&#xff0c;在一定时间内保证一个键&#xff08;key&#xff09;的访问量不超过某个阈值。这里的关键是设置一个令牌桶&#xff0c;在某个时间段内生成一定数量的令牌&#xff0c;然后每次访问时从桶中获取令牌&#xff0c;如果桶中没有令牌&#x…...

【小白专用】php pdo方式连接sqlserver 设置方法 更新23.12.21

windows系统的拓展相对来说比较好安装&#xff0c;直接下载对应的dll文件&#xff0c;修改php.ini配置文件即可。 添加PHP对SQL SERVER的支持 1.新建PHP 文件&#xff0c;输入内容&#xff1a; <?php echo phpinfo(); ?> 2.运行后&#xff0c;可以查看到如下数据&…...

打开C#项目时出现“error : 找不到指定的 SDK”的错误解决方法汇总

从github上克隆项目回来&#xff0c;发现编译通过&#xff0c;我是通过一下步骤解决的&#xff1a; 1.到适用于 Visual Studio 的 .NET SDK 下载网址下载对应版本的.NET SDK&#xff0c;关闭当前VS后安装&#xff1b; 2.参考VS2022报错&#xff1a;error : 找不到指定的 SDK“…...

MIT 6.824 练习1

Hi, there! 这是一份根据 MIT 6.824(2021) 课程的第 2 课的课堂示例代码改编的 2 个 go 语言编程练习。像其他的编程作业一样&#xff0c;我去除了核心部分&#xff0c;保留了代码框架&#xff0c;并编写了每一步的提示 练习代码在本文的最后面 爬虫 在第一部分&#xff0c;…...

Git报错x509: certificate signed by unknown authority

下载报错&#xff1a; Error downloading object: model-00001-of-00008.safetensors (ed3ac49): Smudge error: Error downloading model-00001-of-00008.safetensors (ed3ac4983f682a999b0e4b6f072aad294c4fd9a7e968e90835ba5c4b466d3c7c): LFS: Get https://cdn-lfs.huggin…...

OpenCV技术应用(8)— 如何将视频分解

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。本节课就手把手教大家如何将一幅图像转化成热力图&#xff0c;希望大家学习之后能够有所收获~&#xff01;&#x1f308; 目录 &#x1f680;1.技术介绍 &#x1f680;2.实现代码 &#x1f680;1.技术介绍 视频是…...

Swagger2接口测试文档

目录 一、Swagger简介 1.1 Swagger是什么&#xff1f; 1.2 为什么要用Swagger 1.3 Swagger注解 二、Spring集成Swagger 三、测试环境配置 一、Swagger简介 1.1 Swagger是什么&#xff1f; Swagger 是一个开源的 API 设计和文档工具&#xff0c;它可以帮助开发人员更快、…...

【Java】BigDecimal 比较自动化页面获取数据的大小

jwensh2023.12.20 使用背景 对 web3 相关的数据进行计算的时候&#xff0c;需要进行大小加减计算&#xff0c;UI 自动化过程需要将数据转为自然数&#xff1b;页面获取的数据会有千分位 、高精度(18位) /*** Compares this {code BigDecimal} with the specified* {code BigDe…...

开源键盘工程QMK

一、Qmk简介 目录 一、Qmk简介 二、Qmk入门指导文档 三、QMK配置器 四、QMK层的概念 TMK原先是由Jun Wako设计实现...

Elasticsearch的批量bulk 提交 写入的方式会有顺序问题吗?

Elasticsearch的分布式特性可能会导致写入操作的执行顺序与提交顺序稍有不同。在分布式环境中,Elasticsearch将数据分散到不同的节点上进行存储和处理,因此写入操作的执行顺序可能会受到网络延迟、负载均衡等因素的影响。 根源在于ES的分布式架构。如上图所示,客户端的命令首…...

云原生之深入解析如何使用Vcluster Kubernetes加速开发效率

一、背景 为什么一个已经在使用 Kubernetes 本身方面已经很挣扎的开发人员还要处理虚拟集群呢&#xff1f;答案可能会让您感到惊讶&#xff0c;但虚拟集群实际上比单独的物理集群更容易处理&#xff0c;并且与本地 k3d、KinD 或 minikube 部署的集群相比具有相当多的优势。如果…...

PCL 已知同名点对计算旋转矩阵并对点云进行旋转

目录 一、 算法概述二、代码实现三、测试示例一、 算法概述 适用:已知三组及三组以上的同名点对,计算旋转矩阵;然后根据旋转矩阵对点云进行旋转,最后保存旋转后的点云文件。 二、代码实现 #include <Eigen/Core> #include <Eigen/Dense>...

MyBatis ORM映射

MyBatis只能自动维护库表”列名“与”属性名“相同时的对应关系&#xff0c;二者不同时无法自动ORM 因此需要使用到ORM映射。 共有两种解决办法&#xff1a;1.列的别名 2.结果映射 1.列的别名 在SQL中使用 as 为查询字段添加列别名&#xff0c;以匹配属性名 public List<…...

在线客服系统推荐:为何选择Zoho Desk?

客户和企业的关系并不止于一次买卖关系&#xff0c;企业后续持续的服务不仅是对客户的保障&#xff0c;更能收获良好的品牌口碑和持续的良性收益。所以&#xff0c;企业需要在客户旅程的每一个阶段为客户提供优质服务。而在这段服务旅程中&#xff0c;在线客服系统承担了重要的…...

手绘心情树叶,探索情绪世界

人生如同滚雪球&#xff0c;关键在于找到湿润的雪和陡峭的坡。正如巴菲特所言。昨天参与JSTO的经历&#xff0c;让我深有同感。徐老师分享的主题是《手绘心情树叶&#xff0c;探索情绪世界》&#xff0c;发现在JSTO这个平台上&#xff0c;伙伴们的成长速度惊人。从系统的角度看…...

智能优化算法应用:基于水基湍流算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于水基湍流算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于水基湍流算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.水基湍流算法4.实验参数设定5.算法结果6.…...

打开和关闭GBASE南大通用数据库连接

下面的样例代码使用连接字符串通过GBASE南大通用Connection 类创建连接对象、 打开连接、关闭连接GBASE南大通用。 C# 示例&#xff1a; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; using Sys…...

Zookeeper 集群搭建过程中常见错误

文章目录 Mode: standalone启动失败 Mode: standalone 这通常表示 Zookeeper 配置为单节点模式&#xff0c;而不是集群模式。需要检查 zoo.cfg 文件中的配置&#xff0c;确保包含了所有集群节点的信息。 启动失败 /usr/bin/java ZooKeeper JMX enabled by default Using con…...

Linux开发工具——vim篇

vim开发工具的使用 文章目录 vim开发工具的使用认识vimvim常用三种模式vim正常模式命令集模式切换移动光标删除文字赋值替换撤销上一次操作更改跳到指定的行 vim末行模式命令集列出行号跳到文件中的某一行&#xff1a;保存文件离开vim查找字符&#xff1a; 总结题外话&#xff…...

基于YOLOv5的吸烟检测系统设计与实现

一、项目背景 吸烟检测作为保障公共健康和环境安全的重要任务之一&#xff0c;一直备受关注。传统的吸烟检测方法往往依赖人工判断&#xff0c;存在准确性低和实时性差的问题。为了解决这些问题&#xff0c;本项目基于深度学习技术进行了吸烟检测系统的设计与实现&#xff0c;…...

告别复杂配置:用MobaXterm+网线直连,5分钟让树莓派SSH并上网(Windows环境)

极简主义者的树莓派连接方案&#xff1a;MobaXterm全流程实战指南 树莓派作为一款功能强大的微型计算机&#xff0c;在嵌入式开发、物联网项目和教育领域广受欢迎。然而对于许多初学者甚至有一定经验的开发者来说&#xff0c;如何快速、稳定地连接树莓派始终是个令人头疼的问题…...

波色量子获18.4亿融资,1000量子比特计算机让肿瘤切缘识别、脑电解码大提速!

【导语&#xff1a;科幻电影中令人惊叹的量子计算机&#xff0c;如今已从实验室走向商业化落地。波色量子成立三年获11轮融资&#xff0c;累计金额达18.4亿元。其量子计算能力在多个行业实现应用&#xff0c;尤其在生命科学领域展现出巨大潜力。】波色量子&#xff1a;资本竞逐…...

SIGTRAN协议:电信网络IP化的关键技术解析

1. SIGTRAN&#xff1a;下一代电信网络的信令传输基石2003年全球电信业寒冬中&#xff0c;一个技术决策正在悄然改变行业格局。当运营商们紧缩资本开支时&#xff0c;AT&T、Verizon等巨头却不约而同地加大了对IP网络的投入。这背后隐藏着一个关键技术转折——传统TDM网络向…...

心灵鸡汤01 - 人生九不争

一、跟父母&#xff0c;不争口舌&#xff1b; 二、跟朋友&#xff0c;不争面子&#xff1b; 三、跟领导&#xff0c;不争高低&#xff1b; 四、跟小人&#xff0c;不争道理&#xff1b; 五、跟伴侣&#xff0c;不争对错&#xff1b; 六、跟亲戚&#xff0c;不争穷富&#xff1b…...

别再死记硬背截止、放大、饱和了!用Arduino+面包板,5分钟直观理解NPN/PNP三极管

用Arduino实验破解三极管的三大工作状态之谜 记得第一次翻开电子学教材看到三极管章节时&#xff0c;那些密密麻麻的曲线图和公式让我头皮发麻。"截止区"、"放大区"、"饱和区"——这些抽象概念就像天书一样难以理解。直到有一天&#xff0c;我拿…...

深度解构:指纹浏览器底层隔离与Python高并发RPA,如何重塑电商矩阵自动化架构?

大家好&#xff0c;我是林焱&#xff0c;一名专注电商底层业务逻辑与 RPA 自动化架构定制的独立开发者。 在 CSDN 的各个技术板块中&#xff0c;关于爬虫与反爬虫、并发调度、以及客户端架构的讨论一直是热点。而将这些技术综合应用到极致的领域之一&#xff0c;就是当下极度内…...

社区团购系统源码推荐:为什么越来越多团队开始关注 LikeShop 社区团购系统?

如果你最近在研究&#xff1a;社区团购系统源码社区团购平台搭建团长分销系统私域社区团购社区自提系统你会发现一个现象&#xff1a;越来越多人开始提到&#xff1a;“LikeShop社区团购系统”。尤其是在&#xff1a;生鲜团购社区零售社群团购县域电商社区便利店私域卖货这些场…...

从V100到A100:手把手教你理解Ampere架构的7个关键性能优化点

从V100到A100&#xff1a;手把手教你理解Ampere架构的7个关键性能优化点 如果你正在使用NVIDIA V100进行深度学习训练或高性能计算&#xff0c;那么升级到A100可能已经在你的考虑范围内。但这次升级究竟能带来多少实际性能提升&#xff1f;本文将带你深入Ampere架构的7个核心优…...

从“狗的信”看FPGA设计:工程师的幽默隐喻与EDA实践

1. 从一封“狗的信”到工程师的幽默与哲思那天在EE Times上翻到一篇2011年的老文章&#xff0c;标题是《‘Dear God…’ (From the Dog)》&#xff0c;作者是Clive Maxfield。说实话&#xff0c;在一堆充斥着“3nm工艺”、“HBM4 PHY”、“AI Agent”这些硬核技术词汇的行业新闻…...

从键值对到时序数据:FlashDB在智能家居传感器上的两种实战用法

从键值对到时序数据&#xff1a;FlashDB在智能家居传感器上的两种实战用法 清晨6点&#xff0c;卧室的温湿度传感器悄然启动。它需要在电池耗尽前完成三项任务&#xff1a;读取当前环境数据、检查预设报警阈值、通过LoRaWAN网络上传信息。当网络不稳定时&#xff0c;这些数据必…...