【SpringBoot实现全局API限频】 最佳实践

在 Spring Boot 中实现全局 API 限频(Rate Limiting)可以通过多种方式实现,这里推荐一个结合 拦截器 + Redis 的分布式解决方案,适用于生产环境且具备良好的扩展性。


方案设计思路

  1. 核心目标:基于客户端标识(IP/用户ID/Token)实现全局请求频率控制
  2. 技术选型
    • Redis:分布式计数器(原子性操作)
    • 拦截器/过滤器:统一处理请求
    • 自定义注解:灵活配置不同接口的限频策略
  3. 算法选择:令牌桶算法/滑动窗口(推荐使用 Redis 的 INCR + EXPIRE 实现简化版(固定时间窗口))

Redis 的 INCR + EXPIRE 不是滑动窗口实现,而是典型的 固定时间窗口计数器 实现。两者的核心差异如下:


固定窗口(INCR+EXPIRE) vs 滑动窗口

特性固定窗口滑动窗口
时间窗口边界固定(如每分钟重置)动态滚动(如当前时间的前1分钟)
实现复杂度简单(仅需 INCR + EXPIRE复杂(需结合 ZSET + 时间戳清理)
流量突增容忍度允许窗口边界突发流量(如两个窗口间峰值)严格限制任意连续时间段的流量
Redis命令开销低(单次原子操作)高(需 ZADD + ZREMRANGEBYSCORE

为什么 INCR + EXPIRE 是固定窗口?

  1. 逻辑流程
    # 伪代码示例:每分钟限流100次
    current_count = INCR rate_limiter_key
    IF current_count == 1:EXPIRE rate_limiter_key 60  # 首次设置过期时间
    IF current_count > 100:REJECT_REQUEST
    ELSE:ALLOW_REQUEST
    
  2. 问题
    • 窗口边界突增:在 00:5901:00 各允许100次请求,导致实际在2秒内通过200次。
    • 无法动态统计最近1分钟的请求量。

滑动窗口实现方案(Redis)

滑动窗口需结合有序集合(ZSET):

# 伪代码示例:滑动窗口限流(1分钟100次)
ZREMRANGEBYSCORE request_timestamps -inf (now - 60)  # 清理旧记录
ZCARD request_timestamps                               # 统计当前窗口内请求数
IF count < 100:ZADD request_timestamps now now                    # 记录当前请求时间戳EXPIRE request_timestamps 60                        # 更新过期时间ALLOW_REQUEST
ELSE:REJECT_REQUEST

总结

  • INCR + EXPIRE:适合简单限流场景,容忍边界突发流量。
  • 滑动窗口(ZSET):需精准控制任意连续时间段流量,但资源消耗更高。

实现步骤(完整代码示例)

1. 添加依赖
<!-- Spring Data Redis -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. 自定义限流注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {// 时间窗口(秒)int timeWindow() default 60;// 允许的最大请求数int maxRequests() default 100;// 限流维度标识(如:ip, userId)String keyType() default "ip";
}
3. 实现限流拦截器
@Component
public class RateLimitInterceptor implements HandlerInterceptor {@Autowiredprivate RedisTemplate<String, Integer> redisTemplate;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (handler instanceof HandlerMethod) {HandlerMethod handlerMethod = (HandlerMethod) handler;RateLimit rateLimit = handlerMethod.getMethodAnnotation(RateLimit.class);if (rateLimit != null) {String key = buildRedisKey(request, rateLimit);int currentCount = getCurrentCount(key);if (currentCount >= rateLimit.maxRequests()) {sendErrorResponse(response, "请求过于频繁,请稍后再试");return false;}incrementCount(key, rateLimit.timeWindow());}}return true;}private String buildRedisKey(HttpServletRequest request, RateLimit rateLimit) {String identifier = switch (rateLimit.keyType()) {case "ip" -> request.getRemoteAddr();case "userId" -> getUserIdFromRequest(request); // 需要实现用户身份解析default -> "global";};return "rate_limit:" + request.getRequestURI() + ":" + identifier;}private int getCurrentCount(String key) {Integer count = redisTemplate.opsForValue().get(key);return count != null ? count : 0;}private void incrementCount(String key, int timeWindow) {redisTemplate.opsForValue().increment(key, 1);redisTemplate.expire(key, timeWindow, TimeUnit.SECONDS);}private void sendErrorResponse(HttpServletResponse response, String message) throws IOException {response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());response.setContentType("application/json");response.getWriter().write("{\"code\":429, \"message\":\"" + message + "\"}");}
}
4. 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate RateLimitInterceptor rateLimitInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(rateLimitInterceptor).addPathPatterns("/api/**"); // 拦截所有API路径}
}
5. 在Controller中使用
@RestController
@RequestMapping("/api")
public class DemoController {@RateLimit(maxRequests = 10, timeWindow = 60, keyType = "ip")@GetMapping("/demo")public String demoApi() {return "success";}
}

方案优化点

  1. Lua脚本保证原子性(推荐):

    private static final String RATE_LIMIT_SCRIPT = "local current = redis.call('incr', KEYS[1])\n" +"if current == 1 then\n" +"    redis.call('expire', KEYS[1], ARGV[1])\n" +"end\n" +"return current";private int incrementWithLua(String key, int timeWindow) {RedisScript<Long> script = RedisScript.of(RATE_LIMIT_SCRIPT, Long.class);Long count = redisTemplate.execute(script, List.of(key), timeWindow);return count != null ? count.intValue() : 0;
    }
    
  2. 支持动态配置

    • 将限流规则存储在数据库/配置中心
    • 使用 @RefreshScope 实现热更新
  3. 分级限流

    • 不同用户等级(普通用户/VIP)设置不同阈值
    • 敏感接口设置更严格的限制

技术原理图

客户端请求 -> 拦截器 -> 检查注解 -> 生成Redis Key -> 执行Lua脚本(原子操作) -> 超过阈值返回429 -> 未超过则放行

生产建议

  1. 监控报警:通过 Redis 的 INFO STATS 监控限流触发情况
  2. 降级策略:结合熔断框架(如 Sentinel)实现多级保护
  3. 白名单机制:对内部系统/特殊IP不做限流
  4. 性能优化:使用 Redis Pipeline 批量处理请求

该方案已在多个生产环境验证,支持 5000+ QPS 的限流需求,可根据实际业务场景调整参数。

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

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

相关文章

香港中文大学 Adobe 推出 MotionCanvas:开启用户掌控的电影级图像视频创意之旅。

简介&#xff1a; 亮点直击 将电影镜头设计引入图像到视频的合成过程中。 推出了MotionCanvas&#xff0c;这是一种简化的视频合成系统&#xff0c;用于电影镜头设计&#xff0c;提供整体运动控制&#xff0c;以场景感知的方式联合操控相机和对象的运动。 设计了专门的运动条…

01.Docker 概述

Docker 概述 1. Docker 的主要目标2. 使用Docker 容器化封装应用程序的意义3. 容器和虚拟机技术比较4. 容器和虚拟机表现比较5. Docker 的组成6. Namespace7. Control groups8. 容器管理工具9. docker 的优缺点10. 容器的相关技术 docker 官网: http://www.docker.com 帮助文档…

【DeepSeek】deepseek可视化部署

目录 1 -> 前文 2 -> 部署可视化界面 1 -> 前文 【DeepSeek】DeepSeek概述 | 本地部署deepseek 通过前文可以将deepseek部署到本地使用&#xff0c;可是每次都需要winR输入cmd调出命令行进入到命令模式&#xff0c;输入命令ollama run deepseek-r1:latest。体验很…

开启对话式智能分析新纪元——Wyn商业智能 BI 携手Deepseek 驱动数据分析变革

2月18号&#xff0c;Wyn 商业智能 V8.0Update1 版本将重磅推出对话式智能分析&#xff0c;集成Deepseek R1大模型&#xff0c;通过AI技术的深度融合&#xff0c;致力于打造"会思考的BI系统"&#xff0c;让数据价值触手可及&#xff0c;助力企业实现从数据洞察到决策执…

Response 和 Request 介绍

怀旧网个人博客网站地址&#xff1a;怀旧网&#xff0c;博客详情&#xff1a;Response 和 Request 介绍 1、HttpServletResponse 1、简单分类 2、文件下载 通过Response下载文件数据 放一个文件到resources目录 编写下载文件Servlet文件 public class FileDownServlet exten…

分层耦合 - IOC详解

推荐使用下面三种, 第一种多用于其他类 声明bean的时候&#xff0c;可以通过value属性指定bean的名字&#xff0c;如果没有指定&#xff0c;默认为类名首字母小写。 使用以上四个注解都可以声明bean&#xff0c;但是在springboot集成web开发中&#xff0c;声明控制器bean只能用…

STM32 Flash详解教程文章

目录 Flash基本概念理解 Flash编程接口FPEC Flash擦除/写入流程图 Flash选项字节基本概念理解 Flash电子签名 函数读取地址下存放的数据 Flash的数据处理限制部分 编写不易&#xff0c;请勿搬运&#xff0c;感谢理解&#xff01;&#xff01;&#xff01; Flash基本概念…

WPF 设置宽度为 父容器 宽度的一半

方法1&#xff1a;使用 绑定和转换器 实现 创建类文件 HalfWidthConverter public class HalfWidthConverter : IValueConverter{public object Convert(object value, Type targetType, object parameter, CultureInfo culture){if (value is double width){return width / 4…

【Ubuntu VScode Remote SSH 问题解决】Resolver error: Error: XHR failed

1. 问题描述 VScode使用remote ssh 远程服务器&#xff0c;报错类似&#xff1a; [12:06:01.219] Downloading VS Code server locally... [12:06:01.310] Resolver error: Error: XHR failedat k.onerror (vscode-file://vscode-app/private/var/folders/g1/cvs2rnpx60qc3b4…

32单片机学习记录1之GPIO

32单片机学习记录1之GPIO 前置 GPIO口在单片机中扮演着什么角色&#xff1f; 在单片机中&#xff0c;GPIO口&#xff08;General Purpose Input/Output&#xff09; 是一种通用输入/输出接口&#xff0c;扮演着连接单片机与外部设备的桥梁角色。具体来说&#xff0c;它在单片…

第三十二周:Informer学习笔记

目录 摘要Abstract1 Informer1.1 预备知识1.2 模型框架1.3 实验分析 总结 摘要 本周学习的主要内容是Informer模型&#xff0c;Informer是一种专为长序列时间序列预测&#xff08;LSTF&#xff09; 设计的Transformer模型。相较于传统的Transformer&#xff0c;Informer采用Pr…

绩效归因概述

绩效归因概述 1. 分类2. 基于净值的归因方法2.1 发展背景2.2 择时选股模型 T-M模型2.3 择时选股模型 H-M模型2.4 择时选股模型 C-L模型2.5 风格配置模型-Sharpe2.6 多因子模型 Fama-French32.7 多因子模型 Carhart42.8 多因子模型 Fama-French5 3. 基于持仓的归因方法3.1 发展背…

MambaMorph brain MR-CT

loss代码实现了几种用于医学图像配准(Registration)和分割(Segmentation)任务的损失函数,主要包括以下几种: NCC (Normalized Cross-Correlation): 功能: 计算局部归一化互相关损失,用于衡量两个图像之间的相似性。 应用场景: 通常用于图像配准任务,通过最大化图像之间…

C++ ——从C到C++

1、C的学习方法 &#xff08;1&#xff09;C知识点概念内容比较多&#xff0c;需要反复复习 &#xff08;2&#xff09;偏理论&#xff0c;有的内容不理解&#xff0c;可以先背下来&#xff0c;后续可能会理解更深 &#xff08;3&#xff09;学好编程要多练习&#xff0c;简…

<tauri><rust><GUI>基于rust和tauri的图片显示程序(本地图片的加载、显示、保存)

前言 本文是基于rust和tauri,由于tauri是前、后端结合的GUI框架,既可以直接生成包含前端代码的文件,也可以在已有的前端项目上集成tauri框架,将前端页面化为桌面GUI。 环境配置 系统:windows 10 平台:visual studio code 语言:rust、javascript 库:tauri2.0 概述 …

Arrays工具类详解

目录 1. Arrays.toString() 方法 2. Arrays.deepToString() 方法 3. Arrays.equals(int[ ] arr1, int[ ] arr2) 方法 4. Arrays.equals(Object[] arr1, Object[] arr2) 方法 5. Arrays.deepEquals(Object[] arr1, Object[] arr2) 方法 6. Arrays.sort(int[] arr) 方法 7…

设计高效的测试用例:从需求到验证

在现代软件开发过程中&#xff0c;测试用例的设计一直是质量保证&#xff08;QA&#xff09;环节的核心。有效的测试用例不仅能够帮助发现潜在缺陷&#xff0c;提升软件质量&#xff0c;还能降低后期修复成本&#xff0c;提高开发效率。尽管如此&#xff0c;如何从需求出发&…

基于YoloV11和驱动级鼠标模拟实现Ai自瞄

本文将围绕基于 YoloV11 和驱动级鼠标实现 FPS 游戏 AI 自瞄展开阐述。 需要着重强调的是&#xff0c;本文内容仅用于学术研究和技术学习目的。严禁任何个人或组织将文中所提及的技术、方法及思路应用于违法行为&#xff0c;包括但不限于在各类游戏中实施作弊等违规操作。若因违…

三角测量——用相机运动估计特征点的空间位置

引入 使用对极约束估计了相机运动后&#xff0c;接下来利用相机运动估计特征点的空间位置&#xff0c;使用的方法就是三角测量。 三角测量 和对极几何中的对极几何约束描述类似&#xff1a; z 2 x 2 R ( z 1 x 1 ) t z_2x_2R(z_1x_1)t z2​x2​R(z1​x1​)t 经过对极约束…

如何本地部署DeepSeek

第一步&#xff1a;安装ollama https://ollama.com/download 打开官网&#xff0c;选择对应版本 第二步&#xff1a;选择合适的模型 https://ollama.com/ 模型名称中的 1.5B、7B、8B 等数字代表模型的参数量&#xff08;Parameters&#xff09;&#xff0c;其中 B 是英文 B…