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

后端_基于注解实现的请求限流

前言

在高并发场景下,恶意请求或突发流量可能导致系统过载,请求限流(Rate Limiting)通过控制单位时间内的请求次数,保护系统稳定运行。本文介绍声明式限流的实现原理与实战案例。

1、核心概念与注解说明

请求限流的核心是限制单位时间内的请求次数,通过 @RateLimiter 注解实现声明式限流,无需侵入业务代码。

1.1 @RateLimiter 注解参数

参数名类型说明默认值
countint单位时间内允许的最大请求次数必传
timeint时间窗口大小(单位由 timeUnit 指定)1
timeUnitTimeUnit时间单位(如分钟、秒)TimeUnit.SECONDS
keyResolverClass<? extends KeyResolver>限流维度解析器(如全局、用户ID、IP)DefaultRateLimiterKeyResolver
keyArgString自定义限流Key的Spring EL表达式(配合ExpressionIdempotentKeyResolver使用)

1.2 限流维度解析器

支持多种限流粒度,通过 keyResolver 指定:

解析器类名限流维度适用场景
DefaultRateLimiterKeyResolver全局级别限制接口总请求量(如全局限 100次/分)
UserRateLimiterKeyResolver用户ID级别按用户限制(如单用户 10次/分)
ClientIpRateLimiterKeyResolver客户端IP级别按IP限制(如单IP 5次/分)
ServerNodeRateLimiterKeyResolver服务器节点级别集群中单个节点的请求限制
ExpressionIdempotentKeyResolver自定义表达式复杂维度(如 #user.id + '-' + #type

2、实现原理

限流功能基于 AOP 切面 + Redis 计数器 实现,核心流程如下:

  1. 拦截请求:通过 RateLimiterAspect 切面拦截被 @RateLimiter 注解标记的方法。
  2. 生成限流Key:根据 keyResolver 解析限流维度(如用户ID),结合方法签名生成唯一Redis Key。
  3. 判断是否超限
    • 若未超限:Redis计数器+1,允许请求执行。
    • 若已超限:直接返回限流错误。
  4. 自动过期:Redis Key设置过期时间(与注解的时间窗口一致),自动清理过期计数。

核心原理图示

请求 → AOP切面拦截 → 生成限流Key → Redis计数检查├─ 未超限 → 计数+1 → 执行方法└─ 已超限 → 返回429错误

3、实战案例

3.1 基础使用:全局限流

限制 /user/create 接口每分钟最多10次请求(所有用户共享):

@RestController
public class UserController {@PostMapping("/user/create")// 每分钟最多10次请求(全局维度)@RateLimiter(count = 10, timeUnit = TimeUnit.MINUTES)public String createUser(@RequestBody User user) {userService.createUser(user); // 业务逻辑return "用户创建成功";}
}

3.2 进阶使用:按用户/IP限流

案例1:按用户ID限流

限制单个用户每小时最多5次修改个人信息:

@PostMapping("/user/update")
// 单用户每小时最多5次请求(用户ID维度)
@RateLimiter(count = 5,time = 1,timeUnit = TimeUnit.HOURS,keyResolver = UserRateLimiterKeyResolver.class
)
public String updateUser(@RequestBody User user) {userService.updateUser(user);return "用户更新成功";
}
案例2:按IP限流

限制单个IP每分钟最多3次登录尝试(防暴力破解):

@PostMapping("/login")
// 单IP每分钟最多3次请求(IP维度)
@RateLimiter(count = 3,timeUnit = TimeUnit.MINUTES,keyResolver = ClientIpRateLimiterKeyResolver.class
)
public String login(@RequestParam String username, @RequestParam String password) {return userService.login(username, password);
}
案例3:自定义维度限流

限制同一用户对同一商品的查询,每秒最多2次

@GetMapping("/product/query")
// 自定义Key:用户ID+商品ID,每秒最多2次
@RateLimiter(count = 2,timeUnit = TimeUnit.SECONDS,keyResolver = ExpressionIdempotentKeyResolver.class,keyArg = "#userId + '-' + #productId" // Spring EL表达式
)
public Product queryProduct(Long userId, Long productId) {return productService.getById(productId);
}

3.3 限流效果

当请求超限后,接口返回标准化错误:

{"code": 429,"data": null,"msg": "请求过于频繁,请稍后重试"
}

4、核心代码解析

4.1 AOP切面实现(RateLimiterAspect)

/*** 拦截声明了 {@link RateLimiter} 注解的方法,实现限流操作**/
@Aspect
@Slf4j
public class RateLimiterAspect {/*** RateLimiterKeyResolver 集合*/private final Map<Class<? extends RateLimiterKeyResolver>, RateLimiterKeyResolver> keyResolvers;private final RateLimiterRedisDAO rateLimiterRedisDAO;public RateLimiterAspect(List<RateLimiterKeyResolver> keyResolvers, RateLimiterRedisDAO rateLimiterRedisDAO) {this.keyResolvers = CollectionUtils.convertMap(keyResolvers, RateLimiterKeyResolver::getClass);this.rateLimiterRedisDAO = rateLimiterRedisDAO;}@Before("@annotation(rateLimiter)")public void beforePointCut(JoinPoint joinPoint, RateLimiter rateLimiter) {// 获得 IdempotentKeyResolver 对象RateLimiterKeyResolver keyResolver = keyResolvers.get(rateLimiter.keyResolver());Assert.notNull(keyResolver, "找不到对应的 RateLimiterKeyResolver");// 解析 KeyString key = keyResolver.resolver(joinPoint, rateLimiter);// 获取 1 次限流boolean success = rateLimiterRedisDAO.tryAcquire(key,rateLimiter.count(), rateLimiter.time(), rateLimiter.timeUnit());if (!success) {log.info("[beforePointCut][方法({}) 参数({}) 请求过于频繁]", joinPoint.getSignature().toString(), joinPoint.getArgs());String message = StrUtil.blankToDefault(rateLimiter.message(),GlobalErrorCodeConstants.TOO_MANY_REQUESTS.getMsg());throw new ServiceException(GlobalErrorCodeConstants.TOO_MANY_REQUESTS.getCode(), message);}}}

4.2 Redis计数实现(IdempotentRedisDAO)

/*** 限流 Redis DAO*/
@AllArgsConstructor
public class RateLimiterRedisDAO {/*** 限流操作** KEY 格式:rate_limiter:%s // 参数为 uuid* VALUE 格式:String* 过期时间:不固定*/private static final String RATE_LIMITER = "rate_limiter:%s";private final RedissonClient redissonClient;public Boolean tryAcquire(String key, int count, int time, TimeUnit timeUnit) {// 1. 获得 RRateLimiter,并设置 rate 速率RRateLimiter rateLimiter = getRRateLimiter(key, count, time, timeUnit);// 2. 尝试获取 1 个return rateLimiter.tryAcquire();}private static String formatKey(String key) {return String.format(RATE_LIMITER, key);}private RRateLimiter getRRateLimiter(String key, long count, int time, TimeUnit timeUnit) {String redisKey = formatKey(key);RRateLimiter rateLimiter = redissonClient.getRateLimiter(redisKey);long rateInterval = timeUnit.toSeconds(time);Duration duration = Duration.ofSeconds(rateInterval);// 1. 如果不存在,设置 rate 速率RateLimiterConfig config = rateLimiter.getConfig();if (config == null) {rateLimiter.trySetRate(RateType.OVERALL, count, duration);// 原因参见 https://t.zsxq.com/lcR0WrateLimiter.expire(duration);return rateLimiter;}// 2. 如果存在,并且配置相同,则直接返回if (config.getRateType() == RateType.OVERALL&& Objects.equals(config.getRate(), count)&& Objects.equals(config.getRateInterval(), TimeUnit.SECONDS.toMillis(rateInterval))) {return rateLimiter;}// 3. 如果存在,并且配置不同,则进行新建rateLimiter.setRate(RateType.OVERALL, count, duration);// 原因参见 https://t.zsxq.com/lcR0WrateLimiter.expire(duration);return rateLimiter;}}

5、总结

基于 @RateLimiter 注解的限流方案具有以下优势:

  • 易用性:通过注解声明限流规则,无需编写复杂逻辑。
  • 灵活性:支持全局、用户、IP等多维度限流,满足不同场景。
  • 高性能:基于Redis原子操作,性能损耗低,支持分布式系统。

实际使用时,需根据业务场景合理设置 counttimeUnit(如高频接口设为秒级,低频接口设为分钟级),避免过度限流影响用户体验。

http://www.dtcms.com/a/456659.html

相关文章:

  • 从 0 到 1 搭建 Python 语言 Web UI自动化测试学习系列 10--基础知识 6--元素等待方式和内联框架
  • 织梦网站如何做seo重庆市城市建设档案馆官方网站
  • 一文详解Go语言字符串
  • 通用:MySQL-LBCC并发锁机制
  • Elasticsearch:使用推理端点及语义搜索演示
  • 基于websocket的多用户网页五子棋(九)
  • Async++ 源码分析13--parallel_reduce.h
  • 分布式api调用时间优化和问题排查
  • LeetCode每日一题,20251008
  • h5网站建设的具体内容电子商务平台网站模板
  • hive sql优化基础
  • Linux小课堂: Linux 系统的多面性与 CentOS 下载指南
  • 详解redis,MySQL,mongodb以及各自使用场景
  • 开发网站设计公司建设通网站会员共享密码
  • Linux相关工具vim/gcc/g++/gdb/cgdb的使用详解
  • Verilog和FPGA的自学笔记2——点亮LED
  • uniapp创建ts项目tsconfig.json报错的问题
  • Linux性能调优之内核网络栈发包收包认知
  • 静态网站挂马u钙网logo设计影视剪辑
  • Rust 基础语法指南
  • C11 安全字符串转整数函数详解:atoi_s、atol_s、strtol_s 与 strtoimax_s
  • 从入门到实战:全面解析Protobuf的安装配置、语法规范与高级应用——手把手教你用Protobuf实现高效数据序列化与跨语言通信
  • SaaS版MES系统PC端后台功能清单与设计说明
  • 广州建立公司网站多少钱php网站培训机构企业做网站
  • 若依前后端分离版学习笔记(十九)——导入,导出实现流程及图片,文件组件
  • SSM后台投票网站系统9h37l(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • 基于springboot高校汉服租赁系统的设计与实现(文末附源码)
  • 【AI大模型】WPS 接入DeepSeek 实战项目详解
  • 12306网站为什么做那么差网站的统计代码是什么意思
  • 第二章 预备知识(线性代数)