【SpringBoot实现全局API限频】 最佳实践
在 Spring Boot 中实现全局 API 限频(Rate Limiting)可以通过多种方式实现,这里推荐一个结合 拦截器 + Redis 的分布式解决方案,适用于生产环境且具备良好的扩展性。
方案设计思路
- 核心目标:基于客户端标识(IP/用户ID/Token)实现全局请求频率控制
- 技术选型:
Redis:分布式计数器(原子性操作)拦截器/过滤器:统一处理请求自定义注解:灵活配置不同接口的限频策略
- 算法选择:令牌桶算法/滑动窗口(推荐使用 Redis 的
INCR+EXPIRE实现简化版(固定时间窗口))
Redis 的 INCR + EXPIRE 不是滑动窗口实现,而是典型的 固定时间窗口计数器 实现。两者的核心差异如下:
固定窗口(INCR+EXPIRE) vs 滑动窗口
| 特性 | 固定窗口 | 滑动窗口 |
|---|---|---|
| 时间窗口边界 | 固定(如每分钟重置) | 动态滚动(如当前时间的前1分钟) |
| 实现复杂度 | 简单(仅需 INCR + EXPIRE) | 复杂(需结合 ZSET + 时间戳清理) |
| 流量突增容忍度 | 允许窗口边界突发流量(如两个窗口间峰值) | 严格限制任意连续时间段的流量 |
| Redis命令开销 | 低(单次原子操作) | 高(需 ZADD + ZREMRANGEBYSCORE) |
为什么 INCR + EXPIRE 是固定窗口?
- 逻辑流程:
# 伪代码示例:每分钟限流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 - 问题:
- 窗口边界突增:在
00:59和01: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";}
}
方案优化点
-
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; } -
支持动态配置:
- 将限流规则存储在数据库/配置中心
- 使用
@RefreshScope实现热更新
-
分级限流:
- 不同用户等级(普通用户/VIP)设置不同阈值
- 敏感接口设置更严格的限制
技术原理图
客户端请求 -> 拦截器 -> 检查注解 -> 生成Redis Key -> 执行Lua脚本(原子操作) -> 超过阈值返回429 -> 未超过则放行
生产建议
- 监控报警:通过 Redis 的
INFO STATS监控限流触发情况 - 降级策略:结合熔断框架(如 Sentinel)实现多级保护
- 白名单机制:对内部系统/特殊IP不做限流
- 性能优化:使用 Redis Pipeline 批量处理请求
该方案已在多个生产环境验证,支持 5000+ QPS 的限流需求,可根据实际业务场景调整参数。
相关文章:
【SpringBoot实现全局API限频】 最佳实践
在 Spring Boot 中实现全局 API 限频(Rate Limiting)可以通过多种方式实现,这里推荐一个结合 拦截器 Redis 的分布式解决方案,适用于生产环境且具备良好的扩展性。 方案设计思路 核心目标:基于客户端标识(…...
Day1 25/2/14 FRI
【一周刷爆LeetCode,算法大神左神(左程云)耗时100天打造算法与数据结构基础到高级全家桶教程,直击BTAJ等一线大厂必问算法面试题真题详解(马士兵)】https://www.bilibili.com/video/BV13g41157hK?p3&v…...
开发板适配之I2C-RTC
rx8010时钟芯片挂载在I2C1总线上,并且集成在主控板上。 硬件原理 IOMUX配置 rx8010时钟芯片挂载在I2C1总线上,I2C1数据IIC1_SDA和时钟IIC1_SCL,分别对应的PAD NAME为,UART4_TX_DATA、UART4_RX_DATA。 在arch/arm/boot/dts/imx6u…...
vuedraggable固定某一item的记录
文章目录 基础用法第一种第二种 限制itemdiaggable重新排序交换移动的两个元素的次序每次都重置item的index 基础用法 第一种 <draggable v-model"list" :options"dragOptions"><div class"item" v-for"item in list" :key…...
我的新书《青少年Python趣学编程(微课视频版)》出版了!
🎉 激动人心的时刻来临啦! 🎉 小伙伴们久等了,我的第一本新书 《青少年Python趣学编程(微课视频版)》 正式出版啦! 📚✨ 在这个AI时代,市面上的Python书籍常常过于枯燥&…...
前端开发入门一
前端开发入门一 已经有若干年没有web相关的代码了,以前主要是用C/C编写传统的GUI程序,涉及界面、多线程、网络等知识点。最近准备开发一个浏览器插件,才发现业界已经换了天地,只得重新开始学习了,好在基本的学习能力还…...
Linux(Centos 7.6)命令详解:head
1.命令作用 将每个文件的前10行打印到标准输出(Print the first 10 lines of each FILE to standard output) 2.命令语法 Usage: head [OPTION]... [FILE]... 3.参数详解 OPTION: -c, --bytes[-]K,打印每个文件的前K字节-n, --lines[-],打印前K行而…...
HTTP请求X-Forwarded-For注入
场景描述 当你对用户网站进行的爆破或者sql注入的时候,为了防止你影响服务器的正常工作,会限制你访问,当你再次访问时,会提示你的由于你的访问频过快或者您的请求有攻击行为,限制访问几个小时内不能登陆,并且重定向到一个错误友好提示页面。 由此可以发起联想?http是无状…...
《生息之地》入围柏林主竞赛,总制片人蒋浩助力青年导演走向国际
当地时间2月13日,第75届柏林国际电影节正式开幕。凤凰传奇影业出品的电影《生息之地》已入围主竞赛单元,是本届电影节最受瞩目的华语作品之一,电影总制片人蒋浩、导演霍猛、监制姚晨等主创一同亮相开幕红毯。《生息之地》是导演霍猛继《过昭关…...
实践记录--电脑故障的问题定位和处理回顾--磁盘故障已解决
快速回顾 01-关于系统异常启动的展示信息,目前已经可以通过拍照翻译的方式辅助理解; 02-关于固态磁盘的故障定位,可以尝试通过SSD-Z工具查看分区引导记录信息,通过diskgenius工具进行坏道检测和修复; 03-体验了diskge…...
uni-app 学习(一)
一、环境搭建和运行 (一)创建项目 直接进行创建 (二)项目结构理解 pages 是页面 静态资源 打包文件,看我们想输出成什么格式 app.vue 页面的入口文件 main.js 是项目的入口文件 存放对打包文件的配置 pages 存放整…...
Ubuntu 22.04 Desktop企业级基础配置操作指南
一、网络配置 cd /etc/netplan vi 00-installer-config.yaml 设置如下所示: network:version: 2ethernets:eth0: # 替换为你的实际网络接口名称,如 ens33, enp0s3 等dhcp4: noaddresses:- 192.168.1.100/24 # 静态IP地址和子网掩码gateway4: 192.168.1.254 # 网关地址n…...
QILSTE H4-105LB/5M高亮蓝光LED灯珠 发光二极管LED
H4-105LB/5M:高亮蓝光LED的复杂特性与突发性挑战 在现代电子设备的复杂世界中,H4-105LB/5M型号的高亮蓝光LED以其独特的参数和复杂的特性脱颖而出。这款LED不仅在尺寸上做到了极致精巧,还在光电参数、可靠性测试和实际应用中展现出令人困惑的…...
【Elasticsearch】Elasticsearch检索方式全解析:从基础到实战(一)
文章目录 引言Elasticsearch检索方式概述两种检索方式介绍方式一:通过REST request uri发送搜索参数方式二:通过REST request body发送搜索参数(1)基本语法格式(2)返回部分字段(3)ma…...
封装neo4j的持久层和服务层
目录 持久层 mp 模仿: 1.抽取出通用的接口类 2.创建自定义的repository接口 服务层 mp 模仿: 1.抽取出一个IService通用服务类 2.创建ServiceImpl类实现IService接口 3.自定义的服务接口 4.创建自定义的服务类 工厂模式 为什么可以使用工厂…...
基于Spring Boot的宠物爱心组织管理系统的设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
error: conflicting types for ‘SSL_SESSION_get_master_key’
$ make make all-am make[1]: Entering directory ‘/home/linuxuser/tor’ CC src/lib/tls/libtor_tls_a-tortls_openssl.o In file included from src/lib/tls/tortls_openssl.c:61: ./src/lib/tls/tortls_internal.h:55:8: error: conflicting types for ‘SSL_SESSION_get_…...
测试狗参加国家超级计算成都中心2024年度用户大会
近日,国家超级计算成都中心举办了“数启新篇算领未来”2024年度用户大会。这场盛会汇聚了来自政府部门、科研院所及企业界的百余位领导专家及用户代表,共同探讨高性能计算在科技创新中的赋能作用,探索超算融合领域的创新发展之路。其中&#…...
从2025年起:数字化建站PHP 8.1应成为建站开发的基准线
在数字化浪潮席卷全球的今天,PHP语言仍然保持着Web开发领域的核心地位。根据W3Techs最新统计,PHP驱动着全球78.9%的已知服务端网站。当时间指向2025年,这个拥有28年历史的编程语言将迎来新的发展里程碑——PHP 8.1版本应成为网站开发的最低基准要求,这不仅是技术迭代的必然…...
飞牛OS与昔映OS深度对比
无论是备份珍贵的照片、视频,搭建个人专属的影视库,还是实现高效的文件共享与协作,NAS 都能成为我们的得力助手。而在众多的 NAS 系统中,飞牛 OS 与昔映 OS 凭借各自的特点,吸引了不少用户的关注。今天,咱们…...
UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件
在选煤厂、化工厂、钢铁厂等过程生产型企业,其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进,需提前预防假检、错检、漏检,推动智慧生产运维系统数据的流动和现场赋能应用。同时,…...
使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台
🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...
在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)
考察一般的三次多项式,以r为参数: p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]; 此多项式的根为: 尽管看起来这个多项式是特殊的,其实一般的三次多项式都是可以通过线性变换化为这个形式…...
CSS | transition 和 transform的用处和区别
省流总结: transform用于变换/变形,transition是动画控制器 transform 用来对元素进行变形,常见的操作如下,它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
