接口限流Part 1:固定窗口
接口限流Part 1:固定窗口
在微服务架构中,接口限流是保障系统稳定性的重要手段。当某个接口突然收到大量请求时,如果没有限流保护,可能导致数据库连接池耗尽、内存溢出,甚至整个服务崩溃。
**接口限流(Rate Limiting)**的核心目标很明确:控制每个用户或IP的请求频率,防止恶意攻击和异常流量对系统造成冲击。
一、固定窗口限流原理
固定窗口算法的核心思想很简单:将时间划分为固定长度的窗口,在每个窗口内统计请求次数,超过阈值就拒绝服务 。
工作原理
- 时间窗口:比如设置1分钟为一个窗口
- 计数规则:每个窗口内,同一IP对同一接口的请求次数不能超过设定值
- 重置机制:新窗口开始时,计数器清零重新计数
具体示例
假设限制为每分钟10次请求:
- 10:00-10:01窗口:用户请求8次 → 允许
- 10:01-10:02窗口:用户请求5次 → 允许(新窗口重新计数)
- 10:00-10:01窗口:用户请求12次 → 第11次开始返回429错误
技术实现 :
- 使用Redis存储计数器
- Key格式:
rate_limit:IP:接口路径 - 通过EXPIRE实现窗口重置
二、Redis + Lua 实现方案
为什么选择Redis + Lua?因为Lua脚本在Redis中是原子性执行 的,可以避免并发问题,确保计数器的准确性。
1. 设计限流Key
String key = String.format("rate_limit:%s:%s", ipAddress, apiPath);
// 例如:rate_limit:192.168.1.100:/api/login
设计思路 :
- 使用IP + 接口路径作为复合Key,实现细粒度限流
- 不同接口可以设置不同的限流策略
- Key命名规范,便于监控和调试
2. 核心Lua脚本
-- KEYS[1] = 限流key(如 rate_limit:127.0.0.1:/api/login)
-- ARGV[1] = 限制次数(如 20)
-- ARGV[2] = 窗口时间(如 60秒)
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
-- 步骤1:计数器+1
local current = redis.call("INCR", key)
-- 步骤2:如果是第一次访问,设置过期时间
if current == 1 then
redis.call("EXPIRE", key, window)
end
-- 步骤3:判断是否超过限制
if current > limit then
return 0 -- 拒绝请求
else
return 1 -- 允许请求
end
脚本执行流程 :
- INCR操作 :每次请求都将计数器+1
- EXPIRE设置 :首次访问时设置Key过期时间,实现窗口重置
- 阈值判断 :比较当前计数与限制阈值,决定是否放行
为什么用Lua? 整个流程在Redis中原子执行,避免并发竞态条件。
3. Java集成调用
@Service
public class RateLimitService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 检查是否允许请求
* @param ip 客户端IP
* @param path 接口路径
* @return true-允许,false-拒绝
*/
public boolean isAllowed(String ip, String path) {
String key = String.format("rate_limit:%s:%s", ip, path);
Long result = stringRedisTemplate.execute(
redisScript,
Collections.singletonList(key),
"20", "60" // 限制:20次/60秒
);
return result != null && result == 1;
}
}
使用建议 :
- 可以结合Spring AOP实现注解驱动限流:
@RateLimit(limit=20, window=60) - 建议将限流配置外部化,支持动态调整
- 生产环境建议增加监控告警,关注限流触发情况
至此,一个分布式固定窗口限流器 就完成了!
三、优缺点分析
优点
- 实现简单 :逻辑清晰,易于理解和维护
- 性能优秀 :Redis + Lua 原子操作,响应速度快
- 内存友好 :Key自动过期,不会造成内存泄漏
- 分布式支持 :天然支持多实例部署
缺点
- 边界突刺问题 :
时间轴:|----窗口1----|----窗口2----|
请求: 10:00:59 10:01:00
结果: 第20次 第1次(新窗口)
问题: 在边界时刻可能出现2倍阈值的突发流量 - 限流不够平滑 :窗口重置时计数器清零,缺乏连续性
适用场景
适合 :
- 对精度要求不高的场景
- 需要快速实现的限流需求
- 低频接口的简单限流
不适合 :
- 高精度限流要求
- 需要平滑限流的场景
- 对边界突刺敏感的业务
改进方案
如果遇到边界突刺问题,可以考虑:
- 滑动窗口算法 :平滑限流,消除边界问题
- 令牌桶算法 :支持突发流量,更加灵活
- 漏桶算法 :严格控制输出速率
后续文章将详细介绍这些改进方案,敬请期待!
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Aromatic!



